I am trying to draw text on images with varying sizes. The image is displayed in an ImageView and then drawn on the Canvas. While the Canvas retains the image's original height and width, the image does appear a lot smaller in the ImageView so this makes selecting an appropriate text size for the Canvas very tedious, as the ImageView gives a false perception of how the text will appear on the image.
To come across this problem, I decided to use cross-multiplication to find an appropriate text size for the text on Canvas, which is suppose to appear on the Canvas as it does on the ImageView. Here is my attempt:
textSize = topTextView.getTextSize();
imageViewArea = ((img.getWidth()) * (img.getHeight()));
canvasArea = ((canvas.getWidth()) * (canvas.getHeight()));
x = (((textSize)/(imageViewArea)) * (canvasArea));
The first line gets the size of the text that appears on the ImageView. The 2nd and 3rd lines compute the area of the ImageView and the Canvas. The third line basically puts it all together and (ideally) outputs a float value that is to be the size of the text drawn on the Canvas.
However, it isn't close to how it should be. The text is unintelligible when its drawn on small images. Thin and ugly when its drawn on mid-size images. Basically, it looks nothing like in the preview.
My assumption is: Cross-Multiplication is not the way to go.
Any suggestions?
public Bitmap createMeme(ImageView img){
BitmapDrawable bitmapDrawable = ((BitmapDrawable) img.getDrawable());
Bitmap bitmap = bitmapDrawable.getBitmap();
Bitmap mutableBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
String topText = topTextView.getText().toString();
String bottomText = bottomTextView.getText().toString();
topText = topText.toUpperCase();
bottomText = bottomText.toUpperCase();
Canvas canvas = new Canvas(mutableBitmap);
TextPaint topFillPaint = new TextPaint();
TextPaint bottomFillPaint = new TextPaint();
TextPaint topStrokePaint = new TextPaint();
TextPaint bottomStrokePaint = new TextPaint();
Typeface typeface = getResources().getFont(R.font.impact);
textSize = topTextView.getTextSize();
imageViewArea = ((img.getWidth()) * (img.getHeight()));
canvasArea = ((canvas.getWidth()) * (canvas.getHeight()));
val = textSize * sqrt(canvasArea / imageViewArea);
x = (float)val;
topFillPaint.setColor(Color.WHITE);
topFillPaint.setTextSize(x);
topFillPaint.setTypeface(typeface);
topStrokePaint.setStyle(Paint.Style.STROKE);
topStrokePaint.setStrokeWidth(4);
topStrokePaint.setTextSize(x);
topStrokePaint.setColor(Color.BLACK);
topStrokePaint.setTypeface(typeface);
bottomFillPaint.setColor(Color.WHITE);
bottomFillPaint.setTextSize(x);
bottomFillPaint.setTypeface(typeface);
bottomStrokePaint.setStyle(Paint.Style.STROKE);
bottomStrokePaint.setStrokeWidth(4);
bottomStrokePaint.setColor(Color.BLACK);
bottomStrokePaint.setTextSize(x);
bottomStrokePaint.setTypeface(typeface);
StaticLayout topFillLayout = new StaticLayout(topText, topFillPaint, canvas.getWidth(), Layout.Alignment.ALIGN_CENTER,
0.8f, 0.0f, false);
StaticLayout topStrokeLayout = new StaticLayout(topText, topStrokePaint, canvas.getWidth(), Layout.Alignment.ALIGN_CENTER,
0.8f, 0.0f, false);
StaticLayout bottomFillLayout = new StaticLayout(bottomText, bottomFillPaint, canvas.getWidth(), Layout.Alignment.ALIGN_CENTER,
0.8f, 0.0f, false);
StaticLayout bottomStrokeLayout = new StaticLayout(bottomText, bottomStrokePaint, canvas.getWidth(), Layout.Alignment.ALIGN_CENTER,
0.8f, 0.0f, false);
topFillLayout.draw(canvas);
topStrokeLayout.draw(canvas);
canvas.translate(0, canvas.getHeight() - 50);
bottomFillLayout.draw(canvas);
bottomStrokeLayout.draw(canvas);
return mutableBitmap;
}
OUTPUT
So I've uploaded 4 images with different dimensions to see what the text size is for each of them. Here are my findings:
274 x 184 topTextSize: 0, bottomTextSize: 0
3704 x 2469 topTextSize: 237.58, bottomTextSize: 237.58
640 x 480 topTextSize: 0, bottomTextSize: 0
2560 x 1920 topTextSize: 168, bottomTextSize: 168
For 2 and 4, only the top text shows on the bitmap
For example, here are the values for the image with dimensions 274 x 184:
textSize = 168
imageViewArea = 1728000
canvasArea = 50416
x = 0
The ratio that you calculate is a ratio of areas, not the ratio of lengths. If you scale the image by 2 along each side, you will get an area ratio of 4 (or 1/4, depending on the direction that you consider). The font size should be a length, therefore you should use a length ratio.
If you know that the scaling is uniform (height is scaled equally as width), simply calculate the ratios based on either width or height (not the areas). If it is not, you would need a text rendering method that can stretch the font in one direction. If you want to use some kind of average font size that preserves the area ratio, simply use x = textSize * sqrt(canvasArea / imageViewArea).
Off-topic comment: You have a lot of unnecessary parentheses in your code snippet. Personally, I think that this make reading the code more difficult. I would therefore advise to remove unnecessary parentheses (unless they are used intentionally to convey a certain meaning).
Related
I have a created bitmaps. Sizes are not specific. Sometimes 120x60 , 129x800 , 851x784. Its not have a specific value... I want to make these bitmaps resizing to 512x512 always but without changing original images aspect ratio. And without cropping. New image must have canvas 512x512 and original image must be center without any cropping.
I was resizing my bitmaps with this function but it makes images really bad because image fitting X and Y . I don't want image to fit x and y on same time fits one of it and keeps its aspect ratio.
public Bitmap getResizedBitmap(Bitmap bm, int newWidth, int newHeight) {
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// CREATE A MATRIX FOR THE MANIPULATION
Matrix matrix = new Matrix();
// RESIZE THE BIT MAP
matrix.postScale(scaleWidth, scaleHeight);
// "RECREATE" THE NEW BITMAP
Bitmap resizedBitmap = Bitmap.createBitmap(
bm, 0, 0, width, height, matrix, false);
bm.recycle();
return resizedBitmap;
}
What I have;
What I want;
Ok, so you're really close. I can't test this right now, but basically what needs to be changed is
1) You need to apply the same scale to both X and Y, so you need to pick the smaller one (try the bigger one if that doesn't work).
matrix.postScale(Math.min(scaleWidth, scaleHeight), Math.min(scaleWidth, scaleHeight));
2) The result will be a bitmap where at least one side is 512px large, the other one will be smaller. So you need to add the padding to fit that side to 512px (equally left and right/top and bottom for centering). In order to do so, you need to create an new bitmap of the desired size:
Bitmap outputimage = Bitmap.createBitmap(512,512, Bitmap.Config.ARGB_8888);
3) and lastly depending on what side of the resizedBitmap is 512px you need to draw resizedBitmap to the correct position in outputImage
Canvas can = new Canvas(outputimage);
can.drawBitmap(resizedBitmap, (512 - resizedBitmap.getWidth()) / 2, (512 - resizedBitmap.getHeight()) / 2, null);
Note here, that 512 - resizedBitmap.getWidth() results in 0 and therefor no padding at the side with correct size.
4) Now return outputImage
Here's a simplification in Kotlin that does both the scale and the translation with the matrix, skipping the intermediate bitmap.
Note that it also sets the background color to white for new pixels, which I needed for my image pipeline. Feel free to remove that if you don't need it.
fun resizedBitmapWithPadding(bitmap: Bitmap, newWidth: Int, newHeight: Int) : Bitmap {
val scale = min(newWidth.toFloat() / bitmap.width, newHeight.toFloat() / bitmap.height)
val scaledWidth = scale * bitmap.width
val scaledHeight = scale * bitmap.height
val matrix = Matrix()
matrix.postScale(scale, scale)
matrix.postTranslate(
(newWidth - scaledWidth) / 2f,
(newHeight - scaledHeight) / 2f
)
val outputBitmap = Bitmap.createBitmap(newWidth, newHeight, bitmap.config)
outputBitmap.eraseColor(Color.WHITE)
Canvas(outputBitmap).drawBitmap(
bitmap,
matrix,
null
)
return outputBitmap
}
I am absolutly new in Android development and I have the followind doubt.
I have to draw images one next to each other into a Canvas object.
So let to an example: I have this icon (it is pretty huge and I have to resize it):
So I have to put 3 of these icon one next to each other (adding some white space between an image and the next one).
So I have done something like this:
// Load the 2 images for the creation of the "difficulty graphic":
Bitmap chefHatOk = BitmapFactory.decodeResource(getResources(), R.drawable.chef_hat_ok);
// Where the previus image will be drawn:
Canvas canvas = new Canvas();
So I think that I can add the previous image to the Canvas doing something like this:
canvas.drawBitmap(smallImage, 0f, 0f, null);
I think that the first 0f value represent the horizontal space before the inserted image (the offset), correct me if I am doing wrong assertion.
So, how can I add 3 of these images one next to each other leaving some white space between an image and the next one?
Something like this should work:
Bitmap output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
int space = 10; // the space between images
for(int i = 0; i < 3; i++) {
canvas.drawBitmap(smallImage, i * (smallImage.getWidth() + space), 0, null);
}
// do whatever you want with output
This should do the work, eg for two image with space horizontal
public static mergeImages(Bitmap firstImage, Bitmap secondImage)
{
Bitmap cs;
int width, height;
float space = 60f; // space between image horizontal
if(firstImage.getWidth() > secondImage.getWidth()) {
width = firstImage.getWidth() + secondImage.getWidth();
} else {
width = s.getWidth() + secondImage.getWidth();
}
height = firstImage.getHeight();
cs = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas comboImage = new Canvas(cs);
comboImage.drawBitmap(firstImage, 0f, 0f, null);
comboImage.drawBitmap(secondImage, firstImage.getWidth()+space, 0f, null);
return cs; // result image
}
Trying to resize a bitmap and set to a specific part of an imageview. The imageview is square and I wish to have the bitmap in the bottom right corner. Width to be 10% of imageview and height to be 30%.
int w = imageview.getWidth();
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.vertical_bar_green);
imageview.setImageBitmap(Bitmap.createScaledBitmap(bm, w/10, w*30/100, false));
imageview.setScaleType(ScaleType.FIT_END);
The result is the bitmap is the full height of the imageview and the width is much larger.
How can I set specific points to place the bitmap?
From the documentation for END (the matrix used by FIT_END).
Compute a scale that will maintain the original src aspect ratio, but
will also ensure that src fits entirely inside dst. At least one axis
(X or Y) will fit exactly. END aligns the result to the right and
bottom edges of dst.
You will probable want to use a custom matrix for this, probably built with setRectToRect().
For example:
Matrix matrix = new Matrix();
RectF from = new RectF(0, 0, bm.getIntrinsicWidth(), bm.getIntrinsicHeight());
RectF to = new RectF(view.getWidth() * 0.9, view.getHeight() * 0.7, view.getWidth(), view.getHeight());
matrix.setRectToRect(from, to, Matrix.ScaleToFit.FILL);
view.setScaleType(ScaleType.MATRIX);
view.setImageMatrix(matrix);
(I'm not sure if you wanted to keep the original proportions or not, if you want it then use FIT_END for setRectToRect()).
Well, all this thing tortures me for long weeks, I set an Image 227 pixels high in scales it to 170 pixels even if I want it to be wrap_content whenever I do.
Ok. Here I take My Image which is 1950 pixels long (I put here a part of it so you can understand how it should look like).
First, I want to scale it back to 227 pixels high because that's how it was designed and how it should be
Bitmap bitmapOrg = BitmapFactory.decodeResource(getResources(),R.drawable.ver_bottom_panel_tiled_long);
int width = bitmapOrg.getWidth();
int height = bitmapOrg.getHeight();
int newWidth = 200; //this should be parent's whdth later
int newHeight = 227;
// calculate the scale
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// create a matrix for the manipulation
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(scaleWidth, scaleHeight);
// recreate the new Bitmap
Bitmap resizedBitmap = Bitmap.createBitmap(bitmapOrg, 0, 0,
width, height, matrix, true);
BitmapDrawable dmpDrwbl=new BitmapDrawable(resizedBitmap);
verbottompanelprayer.setBackgroundDrawable(dmpDrwbl);
so... it's not a cropped image at all - no, it's 1950 pixels pressed into 200 pixels.
But I want just cut anything besides this 200 pixels or whatever width I'll set - crop it and not press all this long image into 200 pixels area.
Also, BitmapDrawable(Bitmap bitmap); and imageView.setBackgroundDrawable(drawable); are deprecated - how can I change that?
according to what i see, you create a bitmap of the new size (200x227) , so i'm not sure what you expected. you've even written in the comments that you scale and no word on cropping...
what you can do is :
if the API is at least 10 (gingerbread) , you can use BitmapRegionDecoder , using decodeRegion :
if the API is too old, you need to decode the large bitmap, and then crop it into a new bitmap, using Bitmap.createBitmap
something like this:
final Rect rect =...
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD_MR1)
{
BitmapRegionDecoder decoder=BitmapRegionDecoder.newInstance(imageFilePath, true);
croppedBitmap= decoder.decodeRegion(rect, null);
decoder.recycle();
}
else
{
Bitmap bitmapOriginal=BitmapFactory.decodeFile(imageFilePath, null);
croppedBitmap=Bitmap.createBitmap(bitmapOriginal,rect.left,rect.top,rect.width(),rect.height());
}
I need to create a compass on an app i am working on. So i tried to create a new view called CompassView which basically extends imageview, shows a bitmap that has east west north south pointed on it, uses sensors to find the degrees the phone is pointed at, and rotate the image accordingly so that it would create an actual compass. But the problem is if i try to rotate the image to some angles like 45 degrees, it shrinks down. Here are some images to explain it better.
As you can see, the second image is shrinked down when i try to rotate around 45. What i want it to do is this:
Here is the code i am currently using:
Bitmap bMap = BitmapFactory.decodeResource(getResources(),
R.drawable.compass);
Matrix xMatrix = new Matrix();
xMatrix.reset();
xMatrix.postRotate(360-mValue, 75, 75); //This is 75 because 150 is the image width
Bitmap bMapRotate = Bitmap.createBitmap(bMap, 0, 0,
bMap.getWidth(), bMap.getHeight(), xMatrix, true);
setImageBitmap(bMapRotate);
Any help would be appreciated. THanks
EDIT: (SOLUTION)
I finally got it working thanks to the accepted answer. Here is the code i am using for anyone who wants to know how it worked:
RotateAnimation rAnimAntiClockWise = new RotateAnimation(
360 - mValue, 360 - event.values[0],
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
//mValue is the angle in degrees and i subtracted it from 360 to make it anticlockwise, and event.values[0] is the same thing as mValue
rAnimAntiClockWise.setFillAfter(true);
rAnimAntiClockWise.setInterpolator(new LinearInterpolator());
rAnimAntiClockWise.setDuration(0);
startAnimation(rAnimAntiClockWise);
You can use a alternative trick which will work like same as rotate and doesn't resize the image. I actually rotate the image with 45 degree angle and remain changes after animation.
rAnimAntiClockWise = new RotateAnimation(0.0f, 45.0f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
rAnimAntiClockWise.setFillAfter(true);
rAnimAntiClockWise.setInterpolator(new LinearInterpolator());
bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.rotate);
rAnimAntiClockWise.setDuration(100);
img_rotate.startAnimation(rAnimAntiClockWise);
The issue is that your new image is actually larger, due to the corners of the source "sticking out", and so the view is scaling it down to fit.
A few possible approaches:
After the above code, call Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height), copying the center region of the correct size. Easy given the code you have, but creates a useless intermediate bitmap.
Instead of giving the transform and source image to createBitmap, just create a mutable Bitmap of the correct size , wrap it in a Canvas , and tell the Canvas to render the rotated image .
bMapRotate = Bitmap.createBitmap(
bMap.getWidth(), bMap.getHeight(), bMap.getConfig());
Canvas canvasRotate = new Canvas(bMap);
canvasRotate.drawBitmap(bMap, xMatrix, paint); // any opaque Paint should do
Keep the code you have, but tell the view to crop rather than scale when rendering.