I need to "compute" the resulting bitmap obtained from overlapping two different bitmaps that have an alpha value somewhere between 0 and 255. I need to do this in java code, not xml, because bitmaps are being loaded dinamically and not from resources.
Here is my first try (which always yields a black bitmap...):
private Drawable composeBitmaps(int alpha1, BitmapDrawable bm1,
int alpha2, BitmapDrawable bm2)
{
Canvas c = new Canvas();
Bitmap b1 = bm1.getBitmap();
BitmapDrawable draw1 = new BitmapDrawable(b1.copy(b1.getConfig(), true));
draw1.setAlpha(alpha1);
c.setBitmap(draw1.getBitmap());
Paint p = new Paint();
p.setAlpha(alpha2);
c.drawBitmap(bm2.getBitmap(), 0, 0, p);
return draw1;
}
...
View v = // whatever
v.setBackgroundDrawable(composeBitmaps(100, bdrawable1, 150, bdrawable2));
And the view goes black background. What am I doing wrong?
My code above is absolutely correct. The bug that made the bitmap go black was elsewhere. Found it, fixed it, now it works.
The only thing to note is that my code is slow as hell and it cannot be used to crossfade two fullscreen bitmaps at a reasonable fps rate.
Related
I'm developing a simple 2D Android game and I want to draw the background of each level when the level begins. The background will be static for each level, it won't animate or move.
At first, I was drawing the background in a custom view using canvas and I was loading that view as the background of the level. That worked, however I noticed that if I draw a lot of lines and shapes, the UI begins to stutter. I solved the problem by drawing the background via canvas and saving it directly into a bitmap, which I then load into a background ImageView as you can see below:
CustomView bgView = new CustomView(this); //this view draws the background
Bitmap bitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
bgView.draw(canvas);
ImageView imageViewBg = (ImageView) findViewById(R.id.imageViewBg);
imageViewBg.setImageBitmap(bitmap);
The problem that I notice with this approach is that I'm creating a bitmap with a size of [screenWidth x screenHeight] which might cause an OutOfMemoryError.
So, what's the best approach for drawing the background of a full-screen game? The problem is that I want to draw it dynamically, I cannot load it directly from the assets. (I could use the Bitmap.Config.RGB_565 profile, but still it will just reduce the size of the bitmap in half, it's not the optimal solution).
I use Bitmap.createScaledBitmap():
Bitmap b = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.background),WIDTH,HEIGHT,false);
Get the width and height of the screen
Then draw it as usual:
canvas.drawBitmap(b, 0, 0, null);
EDIT:
Load the bitmap any way you would like. For the purposes of the example, the Bitmap-object will be called b. So first:
WIDTH = b.getWidth();
HEIGHT = b.getHeight();
Move on to rendering:
public void render(Canvas c){
final float scaleFactorX = getWidth()/(WIDTH*1.f);
final float scaleFactorY = getHeight()/(HEIGHT*1.f);
//check if the canvas is null first.
int savedState = c.getSaveCount();
c.scale(scaleFactorX, scaleFactorY);
//draw your bitmap.
c.restoreToCount();
//Do normal rendering
}
Explanation
What I do here is to scale the rendering only for the bitmap. This means anything else rendered will not be affected by this.
We need to get the scale factors to scale the screen to when rendering the Bitmap:
final float scaleFactorX = getWidth()/(WIDTH*1.f);
final float scaleFactorY = getHeight()/(HEIGHT*1.f);
Then save the state of the canvas to reload later.
int savedState = c.getSaveCount();
Now we scale the bitmap using the scaleFactors previously defined
c.scale(scaleFactorX, scaleFactorY);
And restore the bitmap to its original state to draw everything else in whatever type of scale you need. If you do not need to zoom the bitmap(if you have zoom) you can add that scaling after the background-scaling.
c.restoreToCount();
My goal is to have a "green screen" effect for an ImageView and a border. I have a png border with a somewhat thick solid outline, the shape is irregular, and is transparent both inside and outside the border. I want to use it as a border of another image, say a photo, and because the border shape is irregular, some parts of the photo may lie outside of the border.
All this contraption sits at the center of a layout with a complex gradient background, so I cannot just cheat the border imageview and fill the outside part with color similar to the underlying parent background.
My initial idea is to use some image editing tool to fill the inside and outside of the png with different colors (green inside, black outside), place the image behind it, and perform some pixel magic with the resulting bitmap such that all black pixels outside the border will "punch through" the whole canvas thus showing the parent background (the complex gradient background), and all green pixels inside the border will become transparent and reveal the photo behind it.
This sounds somewhat too complicated for a simple-looking goal so I'm postponing the implementation (not even sure if it's possible) until I'm convinced that:
A. There is no existing library I can use with the same effect
B. There is a better solution that results to the same effect, but is a lot less complex.
Got it to work using Bitmap.setPixel
public static Bitmap combine(Bitmap b1, Bitmap b2) {
try {
int maxWidth = b1.getWidth();
int maxHeight = b1.getHeight();
Bitmap bmOverlay = Bitmap.createBitmap(maxWidth, maxHeight, b1.getConfig());
Canvas canvas = new Canvas(bmOverlay);
canvas.drawBitmap(b2, 0, 0, null);
canvas.drawBitmap(b1, 0, 0, null);
for (int index = 0; index < bmOverlay.getWidth(); index++) {
for (int index2 = 0; index2 < bmOverlay.getHeight(); index2++) {
if (bmOverlay.getPixel(index, index2) == Color.rgb(0, 255, 0)) { // or whatever outside color you set on your png
bmOverlay.setPixel(index, index2, Color.TRANSPARENT);
}
}
}
return bmOverlay;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
The trick is to leave the inside of the border as transparent, then fill the outside with a solid color (such as #00ff00) with a tolerance of 0. Won't work as well with shadowed or alpha gradient borders, but should be fine for solid colors.
Usage:
Bitmap b1 = BitmapFactory.decodeResource(getResources(), R.drawable.profile_pic_border_alt);
Bitmap b2 = BitmapFactory.decodeResource(getResources(), R.drawable.panggap);
b2 = ThumbnailUtils.extractThumbnail(panggap, border.getWidth(), border.getHeight());
Bitmap b = combine(b1, b2);
imageView.setImageBitmap(b);
Try to using ColorFilter to do that: http://developer.android.com/intl/es/reference/android/graphics/ColorFilter.html
In this discussion you should find what you want: Applying ColorFilter to ImageView with ShapedDrawable
I have a list of items where I am displaying a bitmap next to the item's name. This bitmap is to be created from 2 images, I have a background image with a smaller foreground image to add on top of the background.
I am seeing that the background image appears to not be present on some of my rows in my list. It is not consistent when and which row has the combined bitmap without the background. It is not always the same row where the combined bitmap does not have the background and it is not always the first or not always the last row where the bitmap does not have the background. And sometimes the whole list has every row with the correct image.
The image below is a mockup showing my issue.
My code for creating the combined bitmap is as follows.
Bitmap combinedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas combinedCanvas = new Canvas(combinedBitmap);
// Add the first bitmap to the canvas (this is my background and this is what appears to be
// missing on some rows in my list on some occasions)
combinedCanvas.drawBitmap(backgroundBitmap, 0, 0, null);
// my second smaller image, on top of the first image but 1 pixel in
// from the left and 20 pixels down from the top
combinedCanvas.drawBitmap(foregroundBitmap, 1, 20, null);
return combinedBitmap;
Note: My backgroundBitmap is generated from a Drawable using the following code
Bitmap backgroundBitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getMinimumHeight(),
Bitmap.Config.ARGB_8888);
backgroundBitmap.setDensity(resources.getDisplayMetrics().densityDpi);
Canvas canvas = new Canvas(backgroundBitmap);
drawable.draw(canvas);
Any suggestions of what I have wrong or even where to look to try and resolve this would be greatly appreciated.
EDIT: I have tested adding a colour to the background of my combinedCanvas to try and see where the image generation is going wrong by adding the following code
// TEMP: fill the canvas in red for now so I can see which combinedBitmaps are missing
// the background image
combinedCanvas.drawColor(Color.RED);
Now the rows which do not have the background are coloured in red. This indicates that the code above to create the combined canvas is somehow not adding the backgroundBitmap. I have checked and my background image is not null for every row in my list.
This method works fine for me. It's in C# (Xamarin), you'll have to translate it to Java I'm afraid.
public static Bitmap CombineImages(Bitmap background, Bitmap foreground)
{
int width = background.Width, height = background.Height;
Bitmap cs = Bitmap.CreateBitmap(width, height, Bitmap.Config.Argb8888);
Canvas comboImage = new Canvas(cs);
background = Bitmap.CreateScaledBitmap(background, width, height, true);
comboImage.DrawBitmap(background, 0, 0, null);
int top = (int)(0.05 * height);
int left = (int)(width - (foreground.Width + (width * 0.05)));
comboImage.DrawBitmap(foreground, left, top, null);
return cs;
}
The left and top are hardcoded for my requirements, it would be better to pass them in as arguments.
I'm working with a background drawable that I want flipped horizontally (it represents the "back side" of a view). I'd like to do this without making a copy of the original bitmap (memory concerns), and I can't just destroy the original because I'll need it later (when the view is flipped back over).
I tried doing this by extending BitmapDrawable and doing some matrix transformations in the draw(Canvas) method, but that doesn't work when hardware accelerated.
Here's what I'm doing now as part of my Fragment's onCreateView():
BitmapDrawable backgroundDrawable = foo;
Matrix matrix = new Matrix();
matrix.preScale(-1, 1);
Bitmap flipped = Bitmap.createBitmap(
backgroundDrawable.getBitmap(), 0, 0, backgroundDrawable
.getBitmap().getWidth(), backgroundDrawable
.getBitmap().getHeight(), matrix, false);
v.findViewById(R.id.bar).setBackgroundDrawable(
new BitmapDrawable(getResources(), flipped));
It works perfectly except that I now have a duplicate Bitmap in memory. Any way around this?
I want to set a background of a View with a tiled bitmap, but the tiling needs to be anchored to the bottom-left, instead of the top-left corner (the default). For example, if the tiles are the smiley faces below, I want it to be tiled like:
Using xml drawables I could achieve either tiling (using tileMode="repeat") or bottom positioning (using gravity="bottom"), but combining both is not possible, even the documentation says so:
android:tileMode
Keyword. Defines the tile mode. When the tile mode is
enabled, the bitmap is repeated. Gravity is ignored when the tile mode
is enabled.
Although it's not internally supported, is there any way to achieve this, perhaps using custom views?
Another way would be to extend BitmapDrawable and override the paint() method:
In this method we avoid creating a new bitmap having the size of the view.
class MyBitmapDrawable extends BitmapDrawable {
private Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
private boolean mRebuildShader = true;
private Matrix mMatrix = new Matrix();
#Override
public void draw(Canvas canvas) {
Bitmap bitmap = getBitmap();
if (bitmap == null) {
return;
}
if (mRebuildShader) {
mPaint.setShader(new BitmapShader(bitmap, TileMode.REPEAT, TileMode.REPEAT));
mRebuildShader = false;
}
// Translate down by the remainder
mMatrix.setTranslate(0, getBounds().bottom % getIntrinsicHeight());
canvas.save();
canvas.setMatrix(mMatrix);
canvas.drawRect(getBounds(), mPaint);
canvas.restore();
}
}
It can be set to the view like this:
view.setBackgroundDrawable(new MyBitmapDrawable(getResources().getDrawable(R.drawable.smiley).getBitmap()));
Just a thought, and it's pretty roundabout, but could you flip your image vertically, and then apply a transform to your background to flip that vertically as well?
Using a custom view might involve handling all the drawing yourself, not just the background image.
Instead, I propose to set the view's background programmatically as shown:
// This drawable refers to an image directly and NOT an XML
BitmapDrawable smiley = (BitmapDrawable) getResources().getDrawable(R.drawable.smiley);
// Create a new bitmap with the size of the view
Bitmap bgBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bgBitmap);
// Translate down by the remainder
Matrix matrix = new Matrix();
matrix.setTranslate(0, view.getHeight() % smiley.getIntrinsicHeight());
canvas.setMatrix(matrix);
// Tile the smileys
Paint paint = new Paint();
paint.setShader(new BitmapShader(smiley.getBitmap(), TileMode.REPEAT, TileMode.REPEAT));
canvas.drawPaint(paint);
view.setBackgroundDrawable(new BitmapDrawable(bgBitmap));
Points to consider:
I'm not sure if view.getWidth() & view.getHeight() are the correct
methods to get the dimensions.
What if smiley size is bigger than the view?