I'm beating my head against a wall here, and I'm fairly certain I'm doing something stupid, so time to make my stupidity public.
I'm trying to take two images, blend them together into a third image using standard blending algorithms (Hardlight, softlight, overlay, multiply, etc).
Because Android does not have such blend properties build in, I've gone down the path of taking each pixel and combine them using an algorithm. However, the results are garbage. Below is the results of a simple multiply blend (images used, and expected result).
BASE:
BLEND:
EXPECTED RESULT:
GARBAGE RESULT:
Any help would be appreciated. Below is the code, which I've tried to strip out all the "junk", but some may have made it through. I'll clean it up if something isn't clear.
ImageView imageView = (ImageView) findViewById(R.id.ImageView01);
Bitmap base = BitmapFactory.decodeResource(getResources(), R.drawable.base);
Bitmap result = base.copy(Bitmap.Config.RGB_565, true);
Bitmap blend = BitmapFactory.decodeResource(getResources(), R.drawable.blend);
IntBuffer buffBase = IntBuffer.allocate(base.getWidth() * base.getHeight());
base.copyPixelsToBuffer(buffBase);
buffBase.rewind();
IntBuffer buffBlend = IntBuffer.allocate(blend.getWidth() * blend.getHeight());
blend.copyPixelsToBuffer(buffBlend);
buffBlend.rewind();
IntBuffer buffOut = IntBuffer.allocate(base.getWidth() * base.getHeight());
buffOut.rewind();
while (buffOut.position() < buffOut.limit()) {
int filterInt = buffBlend.get();
int srcInt = buffBase.get();
int redValueFilter = Color.red(filterInt);
int greenValueFilter = Color.green(filterInt);
int blueValueFilter = Color.blue(filterInt);
int redValueSrc = Color.red(srcInt);
int greenValueSrc = Color.green(srcInt);
int blueValueSrc = Color.blue(srcInt);
int redValueFinal = multiply(redValueFilter, redValueSrc);
int greenValueFinal = multiply(greenValueFilter, greenValueSrc);
int blueValueFinal = multiply(blueValueFilter, blueValueSrc);
int pixel = Color.argb(255, redValueFinal, greenValueFinal, blueValueFinal);
buffOut.put(pixel);
}
buffOut.rewind();
result.copyPixelsFromBuffer(buffOut);
BitmapDrawable drawable = new BitmapDrawable(getResources(), result);
imageView.setImageDrawable(drawable);
}
int multiply(int in1, int in2) {
return in1 * in2 / 255;
}
After reproducing, I think your issue has to do with manipulating the images in RGB565 mode. As discussed in this post, Bitmaps apparently need to be in ARGB8888 mode to manipulate properly. I first got the expected result on a multiply blend by doing the following:
Resources res = getResources();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap base = BitmapFactory.decodeResource(res, R.drawable.base, options);
Bitmap blend = BitmapFactory.decodeResource(res, R.drawable.blend, options);
// now base and blend are in ARGB8888 mode, which is what you want
Bitmap result = base.copy(Config.ARGB_8888, true);
// Continue with IntBuffers as before...
Converting the Bitmaps to ARGB8888 mode did seem to work for me, at least with the gradient test patterns. However, it you only need to do Screen or Multiply, you might try this as well:
// Same image creation/reading as above, then:
Paint p = new Paint();
p.setXfermode(new PorterDuffXfermode(Mode.MULTIPLY));
p.setShader(new BitmapShader(blend, TileMode.CLAMP, TileMode.CLAMP));
Canvas c = new Canvas();
c.setBitmap(result);
c.drawBitmap(base, 0, 0, null);
c.drawRect(0, 0, base.getWidth(), base.getHeight(), p);
With that, you aren't doing the per-pixel calculations, but you are limited to the preset PorterDuff.Modes. In my quick (and dirty) testing, this was the only way I was able to get the blending to work on non-gradient images.
Simple overlay you can do this way (for simplicity it is supposed that bmp1 is equal or bigger than bmp2):
private Bitmap bitmapOverlay(Bitmap bmp1, Bitmap bmp2)
{
Bitmap bmOverlay = Bitmap.createBitmap(bmp1.getWidth(), bmp1.getHeight(), bmp1.getConfig());
Canvas canvas = new Canvas(bmOverlay);
canvas.drawBitmap(bmp1, 0, 0, null);
canvas.drawBitmap(bmp2, 0, 0, null);
return bmOverlay;
}
For more complex blending algorithms, maybe you can help yourself with some available Bitmap/Canvas functions.
Related
I am new to this site, and I come with a question about Android.
Is there any way to convert a Bitmap to grayscale? I know how to draw a grayscale bitmap (using canvas operations: http://www.mail-archive.com/android-developers#googlegroups.com/msg38890.html) but I really need The actual bitmap in gray colors (or at least something that could be converted to a bitmap later on).
Do I have to implement it by hand (pixel by pixel operations)?
I've searched a lot, and still could not find. Anyone knows a easy/efficient way to do it?
Thanks a lot!
OH, yes, it does.
I was using it wrong, thanks for pointing it out to me.
(Sorry for the useless question)
Here is the end code (heavily based on the one linked) since it may help someone:
public Bitmap toGrayscale(Bitmap bmpOriginal)
{
int width, height;
height = bmpOriginal.getHeight();
width = bmpOriginal.getWidth();
Bitmap bmpGrayscale = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bmpGrayscale);
Paint paint = new Paint();
ColorMatrix cm = new ColorMatrix();
cm.setSaturation(0);
ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
paint.setColorFilter(f);
c.drawBitmap(bmpOriginal, 0, 0, paint);
return bmpGrayscale;
}
Any remarks or comments on it are very welcome.
Thanks
If you are going to show that Bitmap on ImageView. Then Instead of converting Bitmap to Gray Scale, you can try below code:
ColorMatrix matrix = new ColorMatrix();
matrix.setSaturation(0);
ColorMatrixColorFilter filter = new ColorMatrixColorFilter(matrix);
imageview.setColorFilter(filter);
For reference
Isn't that exactly what the code you're linking to does? It takes a color bitmap ("bmp"), creates a duplicate bitmap ("bm"), and then draws the color bitmap into "bm" using the filter to turn it into grayscale. From that point on, you can use "bm" as an actual grayscale bitmap and do whatever you want to do with it.
You'd need to tweak the sample a bit (it's using hard-coded sizes, you may want to just clone the size of the original bitmap), but other than that, this seems to be as ready-to-use as it gets, depending on what you want.
I'd like to mention that with this approach one important aspect must be taken in account. BitMap's on Android are stored in the NativeHeap. By just "creating bitmaps", you'll eventually clog the memory, getting an OutOfMemoryException (OOM).
Therefor, the bitmap must always be .recycled().
Here's a more efficient way, which I've made to support all versions of Android:
// https://xjaphx.wordpress.com/2011/06/21/image-processing-grayscale-image-on-the-fly/
#JvmStatic
fun getGrayscaledBitmapFallback(src: Bitmap, redVal: Float = 0.299f, greenVal: Float = 0.587f, blueVal: Float = 0.114f): Bitmap {
// create output bitmap
val bmOut = Bitmap.createBitmap(src.width, src.height, src.config)
// pixel information
var A: Int
var R: Int
var G: Int
var B: Int
var pixel: Int
// get image size
val width = src.width
val height = src.height
// scan through every single pixel
for (x in 0 until width) {
for (y in 0 until height) {
// get one pixel color
pixel = src.getPixel(x, y)
// retrieve color of all channels
A = Color.alpha(pixel)
R = Color.red(pixel)
G = Color.green(pixel)
B = Color.blue(pixel)
// take conversion up to one single value
B = (redVal * R + greenVal * G + blueVal * B).toInt()
G = B
R = G
// set new pixel color to output bitmap
bmOut.setPixel(x, y, Color.argb(A, R, G, B))
}
}
// return final image
return bmOut
}
#TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
#JvmStatic
fun getGrayscaledBitmap(context: Context, src: Bitmap): Bitmap {
// https://gist.github.com/imminent/cf4ab750104aa286fa08
// https://en.wikipedia.org/wiki/Grayscale
val redVal = 0.299f
val greenVal = 0.587f
val blueVal = 0.114f
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
return getGrayscaledBitmapFallback(src, redVal, greenVal, blueVal)
val render = RenderScript.create(context)
val matrix = Matrix4f(floatArrayOf(-redVal, -redVal, -redVal, 1.0f, -greenVal, -greenVal, -greenVal, 1.0f, -blueVal, -blueVal, -blueVal, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f))
val result = src.copy(src.config, true)
val input = Allocation.createFromBitmap(render, src, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT)
val output = Allocation.createTyped(render, input.type)
// Inverts and do grayscale to the image
#Suppress("DEPRECATION")
val inverter =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
ScriptIntrinsicColorMatrix.create(render)
else
ScriptIntrinsicColorMatrix.create(render, Element.U8_4(render))
inverter.setColorMatrix(matrix)
inverter.forEach(input, output)
output.copyTo(result)
src.recycle()
render.destroy()
return result
}
I’m using thquinn’s DraggableGridView and load ~60 images into it. This all works fine. I had all the images needed in my assets, but want to create them at runtime since first of only the Text on the images change which seems redundant and I can reduce the appsize and the second reason is that I need to sometimes change the icons over the Air where adding wouldn’t be the problem but deleting from assets isn’t possible and would use unnecessary space. That briefly to explain my motives here.
So I’ve used the method from this Post to draw text over my Asset png and then convert it into a Bitmap to be able to use them in the LRUcache. This works with a few images but as soon as I try and display all needed Images I get an OutOfMemory error. The Base Images are 200x200 px which I think should also be scaled to the need size depending on screensize and density.
First of, this method doesn’t seem efficient because I create a Bitmap canvas then make a LayerdDrawable which I make into a Bitmap (for caching) again. Not sure, but it just feels like I’m creating to much temp images which clutter up the memory.
And then I’m using a BitmapDrawable which is depreciated. How would this method look without the BitmapDrawable??
Am I going about this the right way in general and How would I make this method efficiently so I don’t get the OOM error?!?
BTW. When I don’t use LRUcache and just return the LayerdDrawable for the GridView the images load fine but I get the Exception after a couple of Orientation changes!
This is the method as I have it atm:
private Bitmap createIcon(Drawable backgroundImage, String text,
int width, int height) {
String key = text.toLowerCase();
Bitmap cachedBitmap = getBitmapFromMemCache(key);
if (cachedBitmap != null){
Log.d("TRACE", "is cached");
return cachedBitmap;
}
else{
Bitmap canvasBitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
Canvas imageCanvas = new Canvas(canvasBitmap);
Typeface font = Typeface.createFromAsset(getActivity().getAssets(), "myriadpro.ttf");
Paint imagePaint = new Paint();
imagePaint.setTextAlign(Paint.Align.CENTER);
imagePaint.setTextSize(26);//
imagePaint.setTypeface(font);
imagePaint.setAntiAlias(true);
imagePaint.setColor(Color.parseColor("#562b12"));
backgroundImage.draw(imageCanvas);
imageCanvas.drawText(text, (width / 2)+4, (height / 2)-8, imagePaint);
LayerDrawable layerDrawable = new LayerDrawable(
new Drawable[]{backgroundImage, new BitmapDrawable(canvasBitmap)});
int w = layerDrawable.getIntrinsicWidth();
int h = layerDrawable.getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
layerDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
layerDrawable.draw(canvas);
addBitmapToMemoryCache(key,bitmap);
return bitmap;
}
}
Update:
I have tried with another method now, which seems better because it’s not using BitmapDrawable. But I still get OOM error. Also it generally doesn’t seem to realy use the cached images, when I change orientation only 1 or 2 images come from the cache.
I also failed to metion before the this is inside a Fragment. Not sure if it matters. But in portrait mode i have only this Fragment and in Landscape there can be another one if the width allows it.
public Bitmap drawTextToBitmap(Context mContext, int resourceId, String mText) {
try {
int memory = (int) (Runtime.getRuntime().freeMemory() / 1024);
Log.d("TRACE", "memory " + memory);
Log.d("TRACE", mText);
String key = mText.toLowerCase();
Bitmap cachedBitmap = getBitmapFromMemCache(key);
if (cachedBitmap != null){
Log.d("TRACE", "is cached");
return cachedBitmap;
}
else{
Resources resources = mContext.getResources();
float scale = resources.getDisplayMetrics().density;
Bitmap bitmap = BitmapFactory.decodeResource(resources, resourceId);
android.graphics.Bitmap.Config bitmapConfig = bitmap.getConfig();
// set default bitmap config if none
if(bitmapConfig == null) {
bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
}
bitmap = bitmap.copy(bitmapConfig, true); // OOE error happens here
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.rgb(110,110, 110));
paint.setTextSize((int) (25 * scale));
Rect bounds = new Rect();
paint.getTextBounds(mText, 0, mText.length(), bounds);
int x = (bitmap.getWidth() - bounds.width())/6;
int y = (bitmap.getHeight() + bounds.height())/5;
canvas.drawText(mText, x * scale, y * scale, paint);
addBitmapToMemoryCache(key,bitmap);
return bitmap;
}
} catch (Exception e) {
// TODO: handle exception
return null;
}
}
I worked on an app which needed to constantly hold 3-4 very large images in memory, and I was struggling with a problem very similar to yours. I loaded a bitmap from a byte array, then I needed to copy it to make it mutable, so that I could draw on it. This copy would cause there to be 1 too many bitmaps in memory, and then cause an OOM crash.
Eventually I found a way to load it once, and make it mutable at the same time:
Options options = new Options();
options.inMutable = true;
BitmapFactory.decodeResource(getResources(), id, options);
Use this, instead of copying the bitmap to make it mutable. I'm not sure if you really need the ARGB_8888 configuration, but if you don't, this should at least improve your memory efficiency.
Note: This will only work with Android 3.0 and above. For versions that need to run on 2.x and above, you can use a little reflection magic to see if the "inMutable" field exists in runtime. While this won't help on pre-3.0 devices, it will still provide a good improvement for most devices (and I've also noticed that devices running 2.x tend to have more memory flexibility).
Here's the code:
Options options = new Options();
options.inPurgeable = true;
options.inInputShareable = true;
Bitmap mutableBitmap = null;
try
{
Options.class.getField("inMutable").set(options, Boolean.TRUE);
Bitmap decodedBitmap = BitmapFactory.decodeResource(getResources(), id, options);
mutableBitmap = decodedBytes;
}
catch (NoSuchFieldException noFieldException)
{
Bitmap decodedBitmap = BitmapFactory.decodeResource(getResources(), id, options);
mutableBitmap = decodedBitmap .copy(decodedBitmap .getConfig(), true);
decodedBitmap .recycle();
}
I have some question about water mark within android code!
Following code showed my idea about WaterMark!
However,It does not work normally.
e.g. only the image end with .png can be watered mark
Is there a scheme about water mark(.jpeg, .jpg, .wbmp, .bmp, .png or others)
protected static Bitmap getDrmPicture(Context context,String path){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap originMap = BitmapFactory.decodeFile (path,options);
Bitmap waterMark = BitmapFactory.decodeResource(context.getResources(), R.drawable.close);
InputStream input;
byte[] b;
Bitmap waterMark = null;
try {
input = context.getResources().openRawResource(R.drawable.lock);
b = new byte[input.available()];
input.read(b);
waterMark = DecodeUtils.requestDecode(jc, b, null);
}catch(IOException e){
}
int w = originMap.getWidth();
int h = originMap.getHeight();
int ww = waterMark.getWidth();
int wh = waterMark.getHeight();
Bitmap newb = Bitmap.createBitmap(w, h,Bitmap.Config.ARGB_8888;);
Canvas cv = new Canvas(newb);
cv.drawBitmap(originMap, 0, 0, null);
cv.drawBitmap(waterMark, w - ww, h - wh, null);
cv.save(Canvas.ALL_SAVE_FLAG);
cv.restore();
return newb;
}
Thanks !
This is the code I use to apply watermark to a jpeg, it should work for you too,
public Bitmap applyWatermarkColorFilter(Drawable drawable) {
Bitmap image = ((BitmapDrawable)drawable).getBitmap();
Bitmap result = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(result);
canvas.drawBitmap(image, 0, 0, null);
Bitmap watermark = BitmapFactory.decodeResource(getResources(), R.drawable.watermark);
canvas.drawBitmap(watermark, image.getWidth()/2 - watermark.getWidth()/2,
image.getHeight()/2 - watermark.getHeight()/2,
null);
return result;
}
Basically after this u have to use Bitmap.compress(<arguments>) to get a jpg out of it.
Din't try for the other formats. May be it might be possible if you can extract the Bitmap out of them like how we do for jpg and png.
https://stackoverflow.com/questions/6756975/draw-multi-line-text-to-canvas
Measure height of multiline text
To center text vertically we need to know text height. Instantiate StaticLayout with text width according to your needs, for us it is simple the width of Bitmap/Canvas minus 16dp padding. The getHeight() then returns height of text.
Positioning text on Canvas
There are four simple steps to position text on Canvas:
Save the current matrix and clip with Canvas.save().
Apply translation to Canvas matrix with Canvas.translate(x,y).
Draw StaticLayout on Canvas with StaticLayout.draw(canvas).
Revert matrix translation with Canvas.restore() method.
I am trying to write a method that will take a Bitmap and force it to a strict black and white image (no shades of grey).
I first pass the bitmap to a method that makes it greyscale using colormatrix:
public Bitmap toGrayscale(Bitmap bmpOriginal)
{
int width, height;
height = bmpOriginal.getHeight();
width = bmpOriginal.getWidth();
Bitmap bmpGrayscale = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
Canvas c = new Canvas(bmpGrayscale);
Paint paint = new Paint();
ColorMatrix cm = new ColorMatrix();
cm.setSaturation(0);
ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
paint.setColorFilter(f);
c.drawBitmap(bmpOriginal, 0, 0, paint);
return bmpGrayscale;
}
that works nice and fast..
then i pass it to another method to force the greyscaled image to a 2 color image (black and white) this method works but obviously it is going through each pixel and that takes a long time:
public Bitmap toStrictBlackWhite(Bitmap bmp){
Bitmap imageOut = bmp;
int tempColorRed;
for(int y=0; y<bmp.getHeight(); y++){
for(int x=0; x<bmp.getWidth(); x++){
tempColorRed = Color.red(imageOut.getPixel(x,y));
Log.v(TAG, "COLOR: "+tempColorRed);
if(imageOut.getPixel(x,y) < 127){
imageOut.setPixel(x, y, 0xffffff);
}
else{
imageOut.setPixel(x, y, 0x000000);
}
}
}
return imageOut;
}
anyone know a faster more efficient way to do this?
Don't use getPixel() and setPixel().
Use getPixels() which will return a multidimensional array of all pixels. Do your operations locally on this array, then use setPixels() to set back the modified array. This will be significantly faster.
Have you tried something like converting it into a byte array (see the answer here)?
And, as I look into this, the Android reference for developers about Bitmap processing might help you as well.
What is possible solution to banded images in Android Activity or in OpenGl.
Look at the answer below.
Hope it Helps
Color Banding Solved ooooooooooyyyyyyyeaaaaaaaaaa
I solved color banding in two phases
1) * when we use the BitmapFactory to decode resources it decodes the resource in RGB565 which shows color banding, instead of using ARGB_8888, so i used BitmapFactory.Options for setting the decode options to ARGB_8888
second problem was whenever i scaled the bitmap it again got banded
2) This was the tough part and took a lot of searching and finally worked
* the method Bitmap.createScaledBitmap for scaling bitmaps also reduced the images to RGB565 format after scaling i got banded images(the old method for solving this was using at least one transparent pixel in a png but no other format like jpg or bmp worked)so here i created a method CreateScaledBitmap to scale the bitmap with the original bitmaps configurations in the resulting scale bitmap(actually i copied the method from a post by logicnet.dk and translated in java)
BitmapFactory.Options myOptions = new BitmapFactory.Options();
myOptions.inDither = true;
myOptions.inScaled = false;
myOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//important
//myOptions.inDither = false;
myOptions.inPurgeable = true;
Bitmap tempImage =
BitmapFactory.decodeResource(getResources(),R.drawable.defaultart, myOptions);//important
//this is important part new scale method created by someone else
tempImage = CreateScaledBitmap(tempImage,300,300,false);
ImageView v = (ImageView)findViewById(R.id.imageView1);
v.setImageBitmap(tempImage);
// the function
public static Bitmap CreateScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)
{
Matrix m = new Matrix();
m.setScale(dstWidth / (float)src.getWidth(), dstHeight / (float)src.getHeight());
Bitmap result = Bitmap.createBitmap(dstWidth, dstHeight, src.getConfig());
Canvas canvas = new Canvas(result);
//using (var canvas = new Canvas(result))
{
Paint paint = new Paint();
paint.setFilterBitmap(filter);
canvas.drawBitmap(src, m, paint);
}
return result;
}
Please correct me if i am wrong.
Also comment if it worked for you.
I am so happy i solved it, Hope it works for you All.
For OpenGl you simply bind the bitmap created after applying upper functions