I have an imageView and several textViews
My app allows user to drag textViews on evey coordinates of imageView (imageView is not full screen) that user wants .
In other words this app allows user to add several captions to user image
and convert that image and captions to a single image and store it on user device.
According to one of stackOverFlow responses I can just convert one textView text to a bitamp
id there any way to screenshot from final image which user have created with its captions in kotlin??
This is my code:
#Throws(IOException::class)
fun foo(text: String) {
val textPaint = object : Paint() {
init {
setColor(Color.WHITE)
setTextAlign(Align.CENTER)
setTextSize(20f)
setAntiAlias(true)
}
}
val bounds = Rect()
textPaint.getTextBounds(text, 0, text.length, bounds)
val bmp = Bitmap.createBitmap(mImgBanner.getWidth(), mImgBanner.getHeight(), Bitmap.Config.RGB_565) //use ARGB_8888 for better quality
val canvas = Canvas(bmp)
canvas.drawText(text, 0, 20f, textPaint)
val path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/image.png"
val stream = FileOutputStream(path)
bmp.compress(Bitmap.CompressFormat.PNG, 100, stream)
bmp.recycle()
stream.close()
}
Add desired views in xml layout inflate it and take screenshot of parent layout that is containing your views.
Code for taking screenshoot:
fun takeScreenshotOfView(view: View, height: Int, width: Int): Bitmap {
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
val bgDrawable = view.background
if (bgDrawable != null) {
bgDrawable.draw(canvas)
} else {
canvas.drawColor(Color.WHITE)
}
view.draw(canvas)
return bitmap
}
you can also use the extension View.drawToBitmap(). It will return a Bitmap
/**
* Return a [Bitmap] representation of this [View].
*
* The resulting bitmap will be the same width and height as this view's current layout
* dimensions. This does not take into account any transformations such as scale or translation.
*
* Note, this will use the software rendering pipeline to draw the view to the bitmap. This may
* result with different drawing to what is rendered on a hardware accelerated canvas (such as
* the device screen).
*
* If this view has not been laid out this method will throw a [IllegalStateException].
*
* #param config Bitmap config of the desired bitmap. Defaults to [Bitmap.Config.ARGB_8888].
*/
fun View.drawToBitmap(config: Bitmap.Config = Bitmap.Config.ARGB_8888): Bitmap {
if (!ViewCompat.isLaidOut(this)) {
throw IllegalStateException("View needs to be laid out before calling drawToBitmap()")
}
return Bitmap.createBitmap(width, height, config).applyCanvas {
translate(-scrollX.toFloat(), -scrollY.toFloat())
draw(this)
}
}
Related
My scenario is iam designing design for print using canvas and my customview to load that canvas.In my case i have created multiple inner canvas inside parent canvas so iam created parentcanvas from activity and attached with one parentbitmap and designed each inner canvas as section by section with startpositon ,at the end i can able see my design on ui but while converting my customview as bitmap and saving in sd storage its doesnt have my design,its showing white as empty while debugging find only my inner created canvas and bitmap images have design but my parent bitmap doesnt contain design.so How Can i get whole design on parent bitmap.
PrintToolActivity:
setContentView(R.layout.activity_printtool)
customView = findViewById(R.id.customPrintView)
customView.setPrintDetails(mappedPrintProfileDetail, mappedPrintFieldDetail, printModel, null)
val tempBitmap = Bitmap.createBitmap(customView.canvasWidth.toInt(), customView.canvasHeight.toInt(), Bitmap.Config.ARGB_8888)
val parentCanvas = Canvas(tempBitmap)
parentCanvas.drawBitmap(tempBitmap, 0f, 0f, null)
customView.parentBitmap = tempBitmap
customView.init(null)
customView.invalidate()
customView.draw(parentCanvas)
customView.saveImage(tempBitmap, customView.bitmapPath)
HeaderSection.kt:
val headerBitmap = Bitmap.createBitmap((printProfileDetail.headerWidth * mmToPixelConvertorValue).toInt(),bitmapHeight.toInt(), Bitmap.Config.ARGB_8888)
val headerCanvas = Canvas(headerBitmap)
headerCanvas.drawColor(-1)
canvas.drawBitmap(headerBitmap, ((canvasWidth/2) - (printProfileDetail.headerWidth * mmToPixelConvertorValue)/2).toFloat() ,0f, paint)
headerCanvas.drawText(headerSegment.colName, (headerSegment.xPosition * mmToPixelConvertorValue).toFloat(), (headerSegment.yPosition * mmToPixelConvertorValue).toFloat(), paint)
DetailSection.Kt:
detailBitmap = Bitmap.createBitmap((printProfileDetail.paperWidth * mmToPixelConvertorValue).toInt(), parentBitmapHeight.toInt(), Bitmap.Config.ARGB_8888)
detailCanvas = Canvas(detailBitmap)
detailCanvas.drawColor(-1)
canvas.drawBitmap(detailBitmap, ((canvasWidth/2) - (printProfileDetail.paperWidth * mmToPixelConvertorValue)/2).toFloat(),canvasTop, paint)
detailCanvas.drawText(colName, (detailSegment.xPosition * mmToPixelConvertorValue).toFloat() , totalYposition.toFloat(), paint)
CustomView.kt:
//Ondraw method
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawColor(-1);
paint.style = Paint.Style.STROKE;
paint.strokeWidth = 2f;
paint.isAntiAlias = true
Controller(canvas,paint,this,parentBitmap).segmentSeparator(printProfileDetail,printFieldDetail,printModel)
}
I'm trying to draw VectorDrawable resource on Canvas. The way I'm doing it now, is a bit complicated, so step-by-step proccess looks like:
Load VectorDrawable from assets, set desired size, convert it to bitmap:
fun getBitmapFromVectorDrawable(context: Context, drawableId: Int, width:
Int, height: Int): Bitmap {
var drawable = ContextCompat.getDrawable(context, drawableId)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
drawable = DrawableCompat.wrap(drawable!!).mutate()
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, width, height)
drawable.isFilterBitmap = true
drawable.draw(canvas)
return bitmap
}
Manually inflate this View:
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="#id/img1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="fitCenter"/>
...
///ACTUALLY, THIS VIEW IS MORE COMPLICATED WITH TextView AND SOME OTHER VIEWS
</RelativeLayout>
Layout it with desired size:
val widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.AT_MOST)
val heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST)
view.measure(widthSpec, heightSpec)
view.layout(0, 0, view.measuredWidth, view.measuredHeight)
Then, I`m setting bitmap to this image View:
imageView.setImageBitmap(getBitmapFromVectorDrawable(view.context, drawableId, width, height))
Then I`m rendering this View into Bitmap, using this method:
fun loadBitmapFromView(v: View, width: Int, height: Int): Bitmap {
v.isDrawingCacheEnabled = true
v.buildDrawingCache();
if (v.drawingCache != null)
return v.drawingCache
val b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val c = Canvas(b)
v.draw(c)
return b
}
And finally, draw it on canvas:
bitmap = loadBitmapFromView(view, width, height)
matrix.reset()
matrix.postTranslate(translation.left, translation.top)
canvas.drawBitmap(bitmap, matrix, null)
My test image is a simple circle. If I'm trying to draw it small (25px:25px), there are a lot of pixel artifacts on edges.
Width and height variables from code above is taken from drawingRect, that I'm trying to draw on canvas, so, at the first step, I`m trying to load resource with target size, that no additional scaling required.
Also, I understand, that there are a better way to draw bitmap on canvas, without using view hierarchy, but I need it to get some aligning benefits from it (align text at center of image, or using constraint layout, autosize text etc...)
EDIT 1
Here is an example:
I'm trying to optimise my app memory usage by following the recommended practice of using BitmapFactory.Options.inBitmap. What I want to do is slice a bitmap into stripes of height stripeHeight like this:
val stripeHeight = 10
val largeBitmap = // the original bitmap
val placeholder = // the bitmap object I want to re purpose for each stripe
val options = BitmapFactory.Options()
options.inBitmap = placeholder
val rows = largeBitmap.height / stripeHeight
for (i in 0 until rows) {
//how can I set the options object into createBitmap?
val stripe = Bitmap.createBitmap(largeBitmap, 0, i * stripeHeight, largeBitmap.width, stripeHeight)
it.onNext(stripe)
}
The problem is Bitmap.createBitmap won't take a BitmapFactory.Options in its method signature. So I'm unsure how to tell android to allocate the new bitmap into the memory space of the previous one.
One approach would be the following:
fun doBitmapProcessing() {
val STRIPE_HEIGHT = 10
//assuming your largeBitmap is a 320x240 ARGB8888 image
val largeBitmap: Bitmap = Bitmap.createBitmap(320, 240, Bitmap.Config.ARGB_8888)
//then allocate a buffer of size 320 * 240 * 4 (4 channels in an ARGB image) bytes
val byteBuffer: ByteBuffer = ByteBuffer.allocate(320 * 240 * 4)
//create a bitmap of desired dimensions for the placeholder
val placeholder: Bitmap = Bitmap.createBitmap(320, STRIPE_HEIGHT, Bitmap.Config.ARGB_8888)
//copy the pixels from your large bitmap to a pre-allocated byte buffer
largeBitmap.copyPixelsToBuffer(byteBuffer)
//create an input stream from the pre-allocated buffer
val inputStream: InputStream = ByteArrayInputStream(byteBuffer.array())
//create the bitmap region decoder
val bitmapRegionDecoder: BitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, true)
//create options, as before
val options = BitmapFactory.Options()
options.inBitmap = placeholder
val rows = largeBitmap.height / STRIPE_HEIGHT
for (i in 0 until rows) {
//call decode stream on the input stream containing your data from *largeImage*, and the options
//in which you specify that you want the memory of *placeholder* to be overwritten.
val region: Rect = Rect(0, i * STRIPE_HEIGHT, largeBitmap.width, i * STRIPE_HEIGHT + STRIPE_HEIGHT)
val stripe: Bitmap = bitmapRegionDecoder.decodeRegion(region, options)
it.onNext(stripe)
}
}
Be sure to read the documentation of BitmapFactory.Options#inBitmap and use the result of BitmapRegionDecoder.decodeRegion(...) instead of placeholder directly, since it is not guaranteed that the same memory location will be used.
There might be another solution through Bitmap.getPixels(...) & Bitmap.setPixels:
fun doBitmapProcessing2() {
val STRIPE_HEIGHT = 10
//assuming your largeBitmap is in ARGB 8888 format
val largeBitmap: Bitmap = Bitmap.createBitmap(320, 240, Bitmap.Config.ARGB_8888)
//create a bitmap of desired dimensions for the placeholder
val placeholder: Bitmap = Bitmap.createBitmap(320, STRIPE_HEIGHT, Bitmap.Config.ARGB_8888)
//create int pixel array in which the colors will be saved
val pixels = IntArray(320 * STRIPE_HEIGHT * 4)
val rows = largeBitmap.height / STRIPE_HEIGHT
for (i in 0 until rows) {
//save colors from desired location into the pixels array
largeBitmap.getPixels(pixels, 0, 320, 0, i * STRIPE_HEIGHT, 320, STRIPE_HEIGHT)
//write the colors saved into the array to the placeholder bitmap
placeholder.setPixels(pixels, 0, 320, 0, 0, 320, STRIPE_HEIGHT)
it.onNext(placeholder)
}
}
Although I'm not so sure about the second one. ( haven't used getPixels/setPixels before)
Hope this helps
Is there some way to compare two Bitmaps which are wrapped by the BitmapDrawable.
The comparison should not fail if the sizes doesn't match, but it should match pixels and the color of the Bitmap
I am not sure how the native part of Android draws the Bitmap, because sameAs returns true even though the tint color is different.
If the size is different, I can create scaled Bitmap from the other and the compare these to.
In my source code I use DrawableCompat.setTint with the ImageViews Drawable and in the test code I load Drawable from resources and tint it same way.
Any ideas? I would like to have test which validates the source Drawable of ImageView and the color as well based on if it's pressed or not.
NOTE 1: My drawables are white, and I use tint to set color. Looping pixels for Bitmapsdoesn't work because they are white at that point, most likely Android native side uses tint color when drawing.
NOTE 2: Using compile 'com.android.support:palette-v7:21.0.0' and Palette.from(bitmap).generate(); doesn't help either because the returned palette has 0 swatches so can't get any color information there.
This is my current matcher:
public static Matcher<View> withDrawable(final Drawable d) {
return new BoundedMatcher<View, ImageView>(ImageView.class) {
#Override
public boolean matchesSafely(ImageView iv) {
if (d == null) {
return iv.getDrawable() == null;
} else if (iv.getDrawable() == null) {
return false;
}
if (d instanceof BitmapDrawable && iv.getDrawable() instanceof BitmapDrawable) {
BitmapDrawable d1 = (BitmapDrawable) d;
BitmapDrawable d2 = (BitmapDrawable) iv.getDrawable();
Bitmap b1 = d1.getBitmap();
Bitmap b2 = d2.getBitmap();
return b1.sameAs(b2);
}
return iv.getDrawable().getConstantState().equals(d.getConstantState());
}
#Override
public void describeTo(Description description) {
description.appendText("with drawable: ");
}
};
}
Thanks.
I use matchers written by Frankie Sardo for this purpose - https://gist.github.com/frankiesardo/7490059. There are a couple of matchers which must solve your issue. All credits to him.
I've had the opposite issue: matching a drawable when it's size and/or tint can change.
When I convert drawable to bitmap, I use it's default size and apply a black tint:
private fun getBitmapFromDrawable(drawable: Drawable): Bitmap {
val bitmap: Bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth,
drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.apply {
setBounds(0, 0, canvas.width, canvas.height)
setTint(Color.BLACK)
draw(canvas)
}
return bitmap
}
Usage:
val bitmap = getBitmapFromDrawable(currentDrawable)
val otherBitmap = getBitmapFromDrawable(otherDrawable)
return bitmap.sameAs(otherBitmap)
You could use a new parameter for matching the drawable color...
private fun getBitmapFromDrawable(drawable: Drawable, color:Int): Bitmap {
val bitmap: Bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth,
drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.apply {
setBounds(0, 0, canvas.width, canvas.height)
setTint(color)
draw(canvas)
}
return bitmap
}
Usage:
val bitmap = getBitmapFromDrawable(currentDrawable, color)
val otherBitmap = getBitmapFromDrawable(otherDrawable, color)
return bitmap.sameAs(otherBitmap)
How I can insert text inside Osmdroid marker? Suppose i need place several markers on map, and each marker should have number inside it. How I can make it? I try #setTitle() method, but it place text in balloon.
Update:
for(int i = 0; i < orders.size(); i++) {
mOrderPoint = orders.get(i).getStart();
final Order order = orders.get(i);
Marker orderMarker = new Marker(mMap);
orderMarker.setIcon(ContextCompat.getDrawable(mContext,R.drawable.order_pin));
orderMarker.setPosition(mOrderPoint);
}
This method takes a drawable from your resources, draws some text on top of it and returns the new drawable. All you need to do is give it the resource id of your bubble, and the text you want on top. Then you can pass the returned drawable wherever you want it.
public BitmapDrawable writeOnDrawable(int drawableId, String text){
Bitmap bm = BitmapFactory.decodeResource(getResources(), drawableId).copy(Bitmap.Config.ARGB_8888, true);
Paint paint = new Paint();
paint.setStyle(Style.FILL);
paint.setColor(Color.BLACK);
paint.setTextSize(20);
Canvas canvas = new Canvas(bm);
canvas.drawText(text, 0, bm.getHeight()/2, paint);
return new BitmapDrawable(bm);
}
Note:
To preserve density you need this constructor
BitmapDrawable (Resources res, Bitmap bitmap)
So, keeping your context, last return should be something like
return new BitmapDrawable(context.getResources(), bm);
This prevent an undesired resized drawable.
You want to look at osmdroid bonus pack.
Website: https://github.com/MKergall/osmbonuspack
There are many examples of stylized bubbles with text in it.
The answer by Blue_Alien does not work with vector drawables in Kotlin. The reason is due to BitmapFactory: it returns an error.
If you are programming in kotlin, there is a significantly simpler solution than using BitmapFactory.
Firstly, get the drawable:
val drawable = ContextCompat.getDrawable(context, R.drawable.order_pin)!!
Then simply use the toBitmap() function:
val bitmap = drawable.toBitmap()
And then the rest of the solution is effectively the same.
Final code:
val drawable = ContextCompat.getDrawable(context, R.drawable.order_pin)!!
val bitmap = drawable.toBitmap()
val paint = Paint();
paint.style = Style.FILL
paint.color = Color.BLACK
paint.textSize = 20
val canvas = Canvas(bitmap)
// Note, this code places the text in the center of the Bitmap (both vertically and horizontally)
// https://stackoverflow.com/a/11121873
canvas.drawText(text, bm.width / 2f,
bm.height / 2f - (paint.descent() + paint.ascent() / 2), paint)
val iconWithText = BitmapDrawable(context.resources, bitmap)
// Marker has to be initialised somewhere in your code
marker.icon = iconWithText
Of course, this code if for one time use. If you want to move it to a function, the parameter could either be the drawable or drawableID, and it would return the icon:
return BitmapDrawable(context.resources, bitmap)