I want to save bitmap without transparent area.
Bitmap has large transparent pixel.
So i want to remove that
How can i do this?
I cant add picture so explain with symbols.
I dont want to crop function.
I hope use filter
┌────────────────────────┐
│ transparent area
│ ┌────────┐
│ crop this
└────────┘
└────────────────────────┘
To find the non-transparent area of your bitmap, iterate across the bitmap in x and y and find the min and max of the non-transparent region. Then crop the bitmap to those co-ordinates.
Bitmap CropBitmapTransparency(Bitmap sourceBitmap)
{
int minX = sourceBitmap.getWidth();
int minY = sourceBitmap.getHeight();
int maxX = -1;
int maxY = -1;
for(int y = 0; y < sourceBitmap.getHeight(); y++)
{
for(int x = 0; x < sourceBitmap.getWidth(); x++)
{
int alpha = (sourceBitmap.getPixel(x, y) >> 24) & 255;
if(alpha > 0) // pixel is not 100% transparent
{
if(x < minX)
minX = x;
if(x > maxX)
maxX = x;
if(y < minY)
minY = y;
if(y > maxY)
maxY = y;
}
}
}
if((maxX < minX) || (maxY < minY))
return null; // Bitmap is entirely transparent
// crop bitmap to non-transparent area and return:
return Bitmap.createBitmap(sourceBitmap, minX, minY, (maxX - minX) + 1, (maxY - minY) + 1);
}
Crop transparent border with this github.
public static Bitmap crop(Bitmap bitmap) {
int height = bitmap.getHeight();
int width = bitmap.getWidth();
int[] empty = new int[width];
int[] buffer = new int[width];
Arrays.fill(empty, 0);
int top = 0;
int left = 0;
int bottom = height;
int right = width;
for (int y = 0; y < height; y++) {
bitmap.getPixels(buffer, 0, width, 0, y, width, 1);
if (!Arrays.equals(empty, buffer)) {
top = y;
break;
}
}
for (int y = height - 1; y > top; y--) {
bitmap.getPixels(buffer, 0, width, 0, y, width, 1);
if (!Arrays.equals(empty, buffer)) {
bottom = y;
break;
}
}
empty = new int[height];
buffer = new int[height];
Arrays.fill(empty, 0);
for (int x = 0; x < width; x++) {
bitmap.getPixels(buffer, 0, 1, x, 0, 1, height);
if (!Arrays.equals(empty, buffer)) {
left = x;
break;
}
}
for (int x = width - 1; x > left; x--) {
bitmap.getPixels(buffer, 0, 1, x, 0, 1, height);
if (!Arrays.equals(empty, buffer)) {
right = x;
break;
}
}
return Bitmap.createBitmap(bitmap, left, top, right - left + 1, bottom - top + 1);
}
I took #Alvaro Menezes's answer and improved it as a Kotlin extension function. I tweaked it a bit, changed some variable names for better readability and it adds more fixes to the issue mentioned by #Ahamadullah Saikat that throws an IllegalArgumentException
Note that reading pixels by line improve a lot the performances against reading this independently as the accepted answer suggest.
/**
* Trims a bitmap borders of a given color.
*
*/
fun Bitmap.trim(#ColorInt color: Int = Color.TRANSPARENT): Bitmap {
var top = height
var bottom = 0
var right = width
var left = 0
var colored = IntArray(width, { color })
var buffer = IntArray(width)
for (y in bottom until top) {
getPixels(buffer, 0, width, 0, y, width, 1)
if (!Arrays.equals(colored, buffer)) {
bottom = y
break
}
}
for (y in top - 1 downTo bottom) {
getPixels(buffer, 0, width, 0, y, width, 1)
if (!Arrays.equals(colored, buffer)) {
top = y
break
}
}
val heightRemaining = top - bottom
colored = IntArray(heightRemaining, { color })
buffer = IntArray(heightRemaining)
for (x in left until right) {
getPixels(buffer, 0, 1, x, bottom, 1, heightRemaining)
if (!Arrays.equals(colored, buffer)) {
left = x
break
}
}
for (x in right - 1 downTo left) {
getPixels(buffer, 0, 1, x, bottom, 1, heightRemaining)
if (!Arrays.equals(colored, buffer)) {
right = x
break
}
}
return Bitmap.createBitmap(this, left, bottom, right - left, top - bottom)
}
Following the official doc:
The new bitmap may be the same object as source, or a copy may have been made.
You should take into account when you execute .recycle() with the source bitmap.
Related
A while back I found this great color picker from Piotr Adams which I can not find on Git anymore but it's still on this page: https://www.programcreek.com/java-api-examples/index.php?source_dir=Random-Penis-master/app/src/main/java/com/osacky/penis/picker/ColorPicker.java
The main reason I use this color picker in my app is because I want to be able to place a pointer on the RadialGradient based on a color. This library calculates the position for a certain color, this means placing a picker on the correct location is very fast and easy.
The problem is I don't quite understand how it works. I now want to generate a RadialGradient with different colors. But the logic it uses does not work when I generate a RadialGradient with different colors.
Here is the code that generates the RadialGradient:
private Bitmap createColorWheelBitmap(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
int colorCount = 12;
int colorAngleStep = 360 / 12;
int colors[] = new int[colorCount + 1];
float hsv[] = new float[]{0f, 1f, 1f};
for (int i = 0; i < colors.length; i++) {
hsv[0] = (i * colorAngleStep + 180) % 360;
colors[i] = Color.HSVToColor(hsv);
}
colors[colorCount] = colors[0];
SweepGradient sweepGradient = new SweepGradient(width / 2, height / 2, colors, null);
RadialGradient radialGradient = new RadialGradient(width / 2, height / 2, colorWheelRadius, 0xFFFFFFFF, 0x00FFFFFF, TileMode.CLAMP);
ComposeShader composeShader = new ComposeShader(sweepGradient, radialGradient, PorterDuff.Mode.SRC_OVER);
colorWheelPaint.setShader(composeShader);
Canvas canvas = new Canvas(bitmap);
canvas.drawCircle(width / 2, height / 2, colorWheelRadius, colorWheelPaint);
return bitmap;
}
The code for listening to changes of the picker, so this calculates the color based on a position:
#Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
int x = (int) event.getX();
int y = (int) event.getY();
int cx = x - getWidth() / 2;
int cy = y - getHeight() / 2;
double d = Math.sqrt(cx * cx + cy * cy);
if (d <= colorWheelRadius) {
colorHSV[0] = (float) (Math.toDegrees(Math.atan2(cy, cx)) + 180f);
colorHSV[1] = Math.max(0f, Math.min(1f, (float) (d / colorWheelRadius)));
selectedPointer.setColor(Color.HSVToColor(colorHSV));
notifyListeners();
invalidate();
}
return true;
case MotionEvent.ACTION_BUTTON_PRESS:
}
return super.onTouchEvent(event);
}
Finally the code that calculates the position based on a color:
// drawing color wheel pointer
float hueAngle = (float) Math.toRadians(colorHSV[0]);
int colorPointX = (int) (-Math.cos(hueAngle) * colorHSV[1] * colorWheelRadius) + centerX;
int colorPointY = (int) (-Math.sin(hueAngle) * colorHSV[1] * colorWheelRadius) + centerY;
float pointerRadius = 0.075f * colorWheelRadius;
int pointerX = (int) (colorPointX - pointerRadius / 2);
int pointerY = (int) (colorPointY - pointerRadius / 2);
colorPointerCoords.set(pointerX, pointerY, pointerX + pointerRadius, pointerY + pointerRadius);
canvas.drawOval(colorPointerCoords, colorPointerPaint);
So my question is how can I for example change the RadialGradient to only include 2 colors, without breaking the calculations of getting the color? Even an explanation on how this works would be great!
There is great tutorial here: http://tekeye.uk/android/examples/ui/android-color-picker-tutorial (not mine). I don't know much about the theory behind it either but you can use this code to calculate color based on position.
// Calculate channel based on 2 surrounding colors and p angle.
private int ave(int s, int d, float p) {
return s + java.lang.Math.round(p * (d - s));
}
// Calculate color based on drawn colors and angle based on x and y position.
private int interpColor(int colors[], float unit) {
if (unit <= 0) {
return colors[0];
}
if (unit >= 1) {
return colors[colors.length - 1];
}
// Adjust the angle (unit) based on how many colors there are in the list.
float p = unit * (colors.length - 1);
// Get starting color position in the array.
int i = (int)p;
p -= i;
// Now p is just the fractional part [0...1) and i is the index.
// Get two composite colors for calculations.
int c0 = colors[i];
int c1 = colors[i+1];
// Calculate color channels.
int a = ave(Color.alpha(c0), Color.alpha(c1), p);
int r = ave(Color.red(c0), Color.red(c1), p);
int g = ave(Color.green(c0), Color.green(c1), p);
int b = ave(Color.blue(c0), Color.blue(c1), p);
// And finally create the color from the channels.
return Color.argb(a, r, g, b);
}
You can call the interpreting function like this for example.
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX() - CENTER_X;
float y = event.getY() - CENTER_Y;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
// Calculate the angle based on x and y positions clicked.
float angle = (float)java.lang.Math.atan2(y, x);
// need to turn angle [-PI ... PI] into unit [0....1]
float unit = angle/(2*PI);
if (unit < 0) {
unit += 1;
}
// mColors is your list with colors so int[].
int color = interpColor(mColors, unit);
break;
}
}
I already tried it in my project and it works like a charm. So hope it helps you too. :)
EDIT:
Oh so my colors are set up like this.
mColors = intArrayOf(-0x10000, -0xff01, -0xffff01, -0xff0001, -0xff0100, -0x100, -0x10000)
So you can add/remove as many colors as you want and since the interpret functions calculates based on size of this array it should work.
I have problem where i need to add a button on center of transparent area in image.
Example:
according to above image i have hundreds of photo frame and each frame there is a transparent area and i need to add a button on center of this transparent area.
Now i want a solution in which i can get coordinates of "x" programatically.
Please help.
public static PointF getTransparentCenter(Bitmap bitmap, Point viewSize) {
List<Point> transparentPoints = new ArrayList<>();
for (int i = 0; i < bitmap.getWidth(); i++) {
for (int j = 0; j < bitmap.getHeight(); j++) {
int pixel = bitmap.getPixel(i, j);
if ((pixel & 0xff000000) == 0) {
//the point color is transparent
transparentPoints.add(new Point(i, j));
}
}
}
int totalX = 0;
int totalY = 0;
for (Point transparentPoint : transparentPoints) {
totalX += transparentPoint.x;
totalY += transparentPoint.y;
}
float centerX = (float) totalX / transparentPoints.size();
float centerY = (float) totalY / transparentPoints.size();
float x = viewSize.x * centerX / bitmap.getWidth();
float y = viewSize.y * centerY / bitmap.getHeight();
return new PointF(x, y);
}
I think this is stupid, but I think there's no other way.
How can I detect black pixel from a circle drawn by canvas.drawcircle?
canvas.drawCircle((float)(myMidPoint.x - myEyesDistance/2.0), myMidPoint.y, (float)30.0, myPaint);
int x1=(int) (myMidPoint.x) /2;
int y1=(int)(myMidPoint.y)/2;
// int x2=(int) (myMidPoint.x + myEyesDistance/2.0);
// int y2=(int) myMidPoint.y;
int pixelColor = myBitmap.getPixel(x1,y1);
if(pixelColor == Color.BLACK) {
//The pixel is black
System.out.println("pixel black");
} else {
//The pixel was white
System.out.println("pixel white");
}
I have asked this question before.
Color.BLACK is the integer representation of argb Hex value 0xff000000. So your statement is checking whether a center point in the circle is exactly the same transparency, red value, blue value and green value as Color.BLACK.
A few options:
You can try comparing just the rgb value by using
if(Color.rgb(Color.red(Color.BLACK), Color.green(Color.BLACK), Color.blue(Color.BLACK) == Color.rgb(Color.red(pixelColor), Color.green(pixelColor), Color.blue(pixelColor))
Alternatively you could scan the entire circle for a black (0x000000) pixel.
Alternatively you could use a Color difference algorithm and you can test different tolerances for what you need. This may help you How to compare two colors for similarity/difference.
The following hasn't been tested, but will give you an idea of which direction you could take also:
//mid points x1 and y1
int x1=(int) (myMidPoint.x) /2;
int y1=(int)(myMidPoint.y)/2;
int radius = 30;
int topPoint = y1 - radius;
int leftPoint = x1 - radius;
int rightPoint = x1 + radius;
int bottomPoint = y1 + radius;
int scanWidth = 0;
for(int i = topPoint; i < bottomPoint; i++)
{
if(i <= y1)
{
scanWidth++;
}
else {
scanWidth--;
}
for(int j = x1 - scanWidth; j < x1 + scanWidth; j++)
{
int pixelColor = myBitmap.getPixel(j,i);
if(Color.rgb(Color.red(Color.BLACK), Color.green(Color.BLACK), Color.blue(Color.BLACK) == Color.rgb(Color.red(pixelColor), Color.green(pixelColor), Color.blue(pixelColor))
{
System.out.println("pixel black");
}
}
}
If I make a picture with mine samsung galaxy s2, the picture is 3264 x 2448 px.
I want to use a color range check on that, but it doesn't work.
However, if I make the picture smaller for example, 2500 x 2500 (so less pixels), then it does work. But I want the use the picture size of the galaxy s2 (3264 x 2448).
I think it is a memory issue?
I don't exactly know anymore what the limit is.
But is their another way to "bypass" this issue?
This is a piece of code, how I do it now:
bmp = BitmapFactory.decodeResource(getResources(),
R.drawable.four_colors);
int width = bmp.getWidth();
int height = bmp.getHeight();
int[] pixels = new int[width * height];
bmp.getPixels(pixels, 0, width, 0, 0, width, height);
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
int index = y * width + x;
int R = (pixels[index] >> 16) & 0xff; //bitwise shifting
int G = (pixels[index] >> 8) & 0xff;
int B = pixels[index] & 0xff;
total++;
if ((G > R)&&(G > B)){
counter++;
}
}
}
It crashes because the picture is to big, smaller pics work.
So is their something to "bypass" this issue? instead of using smaller images :)
I tried some other things, without succes, I try to explain what I tried.
I tried to "cut" the image in two, and then scan it separate (didn't work).
and I tried to only scan the half of it (1632 x 1224) then rotate the image (180 degrees) and scan it again, but also this didn't work out.
Hit me :)
When playing around with huge images you really should be using the BitmapRegionDecoder to process it in chunks.
Edit - now with a simple example:
try {
// Processes file in res/raw/huge.jpg or png
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(getResources().openRawResource(R.raw.huge), false);
try {
final int width = decoder.getWidth();
final int height = decoder.getHeight();
// Divide the bitmap into 1024x768 sized chunks and process it.
int wSteps = (int) Math.ceil(width / 1024.0);
int hSteps = (int) Math.ceil(height / 768.0);
Rect rect = new Rect();
long total = 0L, counter = 0L;
for (int h = 0; h < hSteps; h++) {
for (int w = 0; w < wSteps; w++) {
int w2 = Math.min(width, (w + 1) * 1024);
int h2 = Math.min(height, (h + 1) * 768);
rect.set(w * 1024, h * 768, w2, h2);
Bitmap bitmap = decoder.decodeRegion(rect, null);
try {
int bWidth = bitmap.getWidth();
int bHeight = bitmap.getHeight();
int[] pixels = new int[bWidth * bHeight];
bitmap.getPixels(pixels, 0, bWidth, 0, 0, bWidth, bHeight);
for (int y = 0; y < bHeight; y++){
for (int x = 0; x < bWidth; x++){
int index = y * bWidth + x;
int R = (pixels[index] >> 16) & 0xff; //bitwise shifting
int G = (pixels[index] >> 8) & 0xff;
int B = pixels[index] & 0xff;
total++;
if ((G > R)&&(G > B)){
counter++;
}
}
}
} finally {
bitmap.recycle();
}
}
}
} finally {
decoder.recycle();
}
You can limit the amount of pixel data of the image that you are loading at once by using some of the parameters of the getPixels method. I did something very similar, and I would read them one row at a time, for example.
for(int y = 0; y < height; y++)
{
bitmap.getPixels(pixels, 0, width, 0, y, width, 1);
I am trying to change the alpha value of a bitmap per pixel in a for loop. The Bitmap is created from a createBitmap(source,x,y,w,h) of another bitmap. I've done a little test but I can't seem to alter the alpha. Is it the setPixel command or the fact the bitmap it isn't ARGB?
I want to create a simple fade out effect in the end but for now I am not referencing original pixel colors just green with half alpha. Thanks if you can help :)
_left[1] = Bitmap.createBitmap(TestActivity.photo, 0, 0, 256, 256);
for (int i = 0; i < _left[1].getWidth(); i++)
for (int t = 0; t < _left[1].getHeight(); t++) {
int a = (_left[1].getWidth() / 2) - i;
int b = (_left[1].getHeight() / 2) - t;
double dist = Math.sqrt((a*a) + (b*b));
if (dist > 20) _left[1].setPixel(i, t, Color.argb(128, 0, 255, 0));
}
UPDATE :
Okay this is the result I came up with if anyone wants to take a bitmap and fade out radially. But yes it is VERY SLOW without arrays... Thanks Reuben for a step in the right direction
public void fadeBitmap (Bitmap input, double fadeStartPercent, double fadeEndPercent, Bitmap output) {
Bitmap tempalpha = Bitmap.createBitmap(input.getWidth(), input.getHeight(), Bitmap.Config.ARGB_8888 );
Canvas printcanvas = new Canvas(output);
int radius = input.getWidth() / 2;
double fadelength = (radius * (fadeEndPercent / 100));
double fadestart = (radius * (fadeStartPercent / 100));
for (int i = 0; i < input.getWidth(); i++)
for (int t = 0; t < input.getHeight(); t++) {
int a = (input.getWidth() / 2) - i;
int b = (input.getHeight() / 2) - t;
double dist = Math.sqrt((a*a) + (b*b));
if (dist <= fadestart) {
tempalpha.setPixel(i,t,Color.argb(255, 255, 255, 255));
} else {
int fadeoff = 255 - (int) ((dist - fadestart) * (255/(fadelength - fadestart)));
if (dist > radius * (fadeEndPercent / 100)) fadeoff = 0;
tempalpha.setPixel(i,t,Color.argb(fadeoff, 255, 255, 255));
}
}
Paint alphaP = new Paint();
alphaP.setAntiAlias(true);
alphaP.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
// printcanvas.setBitmap();
printcanvas.drawBitmap(input, 0, 0, null);
printcanvas.drawBitmap(tempalpha, 0, 0, alphaP);
}
The version of Bitmap.createBitmap() you are using returns an immutable bitmap. Bitmap.setPixel() will have no effect.
setPixel is appallingly slow anyway. Aim to use setPixels(), or, best of all, find a better way than manipulating bitmap pixels directly. I expect you could do something clever with a separate alpha-only bitmap and the right PorterDuff mode.