Ok so i was finally able to get user's signature over an image . And through all i was able to learn from canvas and saving an image user drawings , i knew that i had to set the canvas background like so :
mDrawLayout.setBackground(d);
But my issue is : image gets really really stretched from the original one , here s the original image :
heres the out put on the canvas by setting its background : output
heres my mDrawLayout class :
public class DrawingView extends View {
public int width;
public int height;
static Path drawPath;
private Paint drawPaint, canvasPaint;
static int paintColor = 0xFFFF0000;
private float STROKE_WIDTH = 5f;
private Canvas drawCanvas;
private Bitmap canvasBitmap;
private boolean erase=false;
public DrawingView(Context context, AttributeSet attrs){
super(context, attrs);
setupDrawing();
setErase(erase);
}
private void setupDrawing(){
drawPath = new Path();
drawPaint = new Paint();
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(STROKE_WIDTH);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
public void clearDrawing()
{
setDrawingCacheEnabled(false);
onSizeChanged(width, height, width, height);
invalidate();
setDrawingCacheEnabled(true);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
drawCanvas = new Canvas(canvasBitmap);
}
public void setErase(boolean isErase){
erase=isErase;
drawPaint = new Paint();
if(erase) {
setupDrawing();
int srcColor= 0x00000000;
PorterDuff.Mode mode = PorterDuff.Mode.CLEAR;
PorterDuffColorFilter porterDuffColorFilter = new PorterDuffColorFilter(srcColor, mode);
drawPaint.setColorFilter(porterDuffColorFilter);
drawPaint.setColor(srcColor);
drawPaint.setXfermode(new PorterDuffXfermode(mode));
}
else {
setupDrawing();
}
}
//************************************ draw view *************************************************************
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
canvas.drawPath(drawPath, drawPaint);
}
//*************************** respond to touch interaction **************************************************
@Override
public boolean onTouchEvent(MotionEvent event) {
canvasPaint.setColor(paintColor);
float touchX = event.getX();
float touchY = event.getY();
//respond to down, move and up events
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
drawPath.moveTo(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.lineTo(touchX, touchY);
break;
case MotionEvent.ACTION_UP:
drawPath.lineTo(touchX, touchY);
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
break;
default:
return false;
}
//redraw
invalidate();
return true;
}
//*********************************** return current alpha ***********************************************
public int getPaintAlpha(){
return Math.round((float)STROKE_WIDTH/255*100);
}
//************************************** set alpha ******************************************************
public void setPaintAlpha(int newAlpha){
STROKE_WIDTH=Math.round((float)newAlpha/100*255);
drawPaint.setStrokeWidth(newAlpha);
}
heres my canvas class :
public class SignatureActivity extends AppCompatActivity {
SeekBar mThickness;
private AnexoTemporario imagePath;
private DrawingView mDrawLayout;
Button erase, draw;
private Paint drawPaint = new Paint();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_signature);
mThickness = findViewById(R.id.thickness);
mDrawLayout = findViewById(R.id.viewDraw);
erase = findViewById(R.id.erase);
draw= findViewById(R.id.draw);
imagePath = (AnexoTemporario) getIntent().getSerializableExtra("IMAGEURL");
File file = new File(imagePath.getFilePath());
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
Drawable d = new BitmapDrawable(getResources(), bitmap);
mDrawLayout.setVisibility(View.VISIBLE);
mDrawLayout.setDrawingCacheEnabled(true);
mDrawLayout.setEnabled(true);
mDrawLayout.setBackground(d);
mDrawLayout.setRotation(90);
mThickness.setMax(50);
mThickness.setProgress(10);
mDrawLayout.setPaintAlpha(mThickness.getProgress());
int currLevel = mDrawLayout.getPaintAlpha();
mThickness.setProgress(currLevel);
mDrawLayout.invalidate();
erase.setOnClickListener(v -> {
saveDrawing();
drawPaint.setColor(Color.TRANSPARENT);
mDrawLayout.setErase(true);
});
draw.setOnClickListener(v -> mDrawLayout.clearDrawing());
mThickness.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
mDrawLayout.setPaintAlpha(mThickness.getProgress());
}
});
}
public void saveDrawing()
{
Bitmap whatTheUserDrewBitmap = mDrawLayout.getDrawingCache();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
whatTheUserDrewBitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
GetImageTask task = new GetImageTask(whatTheUserDrewBitmap, base64string -> {
try {
WriteSDCard writeSDCard = new WriteSDCard(this);
writeSDCard.writeToSDFile("assinatura.png", base64string.getBytes());
Toast.makeText(this, "Assinatura Salva!", Toast.LENGTH_LONG).show();
Intent intent = new Intent();
intent.putExtra("ASSINATURA", "assinatura.png");
setResult(Activity.RESULT_OK, intent);
finish();
} catch (Exception e) {
//
e.printStackTrace();
Toast.makeText(this, "Erro ao tentar salvar assinatura!", Toast.LENGTH_LONG).show();
}
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
xml file :
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".canvas.SignatureActivity" >
<mobi.stos.gwmobile.canvas.DrawingView
android:id="@ id/viewDraw"
android:layout_width="match_parent"
android:layout_height="0dp"
android:scaleType="centerInside"
app:layout_constraintBottom_toTopOf="@ id/ll"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@ id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<Button
android:id="@ id/erase"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@ id/thickness"
android:layout_weight="1"
android:text="SALVAR"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@ id/draw"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@ id/erase"
android:layout_weight="1"
android:text="APAGAR"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</LinearLayout>
<LinearLayout
android:id="@ id/ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@ id/linearLayout2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="10dp"
android:text="Tamanho da linha" />
<SeekBar
android:id="@ id/thickness"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@ id/viewDraw"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@ id/viewDraw" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
CodePudding user response:
View.setBackground(drawable)
seems like it just applies that Drawable
and scales it to fit the View
. So if that View
is a different aspect ratio than the bitmap, it gets stretched.
The simplest approach would be something like this - stick your background in an ImageView
behind your DrawingView
, and constrain it to the same size as the DrawingView
. Make the scaling CENTER_INSIDE
(to make sure the whole image fits into the ImageView
, while maintaining the aspect ratio) or FIT_CENTER
(same, but it'll also scale up so the image touches the sides):
<!-- specify this one first, so viewDraw is on top of it -->
<ImageView
android:id="@ id/drawingBackground"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="fitCenter"
app:layout_constraintTop_toTopOf="@ id/viewDraw"
app:layout_constraintBottom_toBottomOf="@ id/viewDraw"
app:layout_constraintEnd_toEndOf="@ id/viewDraw"
app:layout_constraintStart_toStartOf="@ id/viewDraw" />
<mobi.stos.gwmobile.canvas.DrawingView
android:id="@ id/viewDraw"
android:layout_width="match_parent"
android:layout_height="0dp"
android:scaleType="centerInside"
app:layout_constraintBottom_toTopOf="@ id/ll"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
And then you can use setImageBitmap
on that ImageView
to display a pic.
The other approach would be passing and storing the background bitmap in DrawingView
, and drawing that to the Canvas
as part of the onDraw
call, using drawBitmap
. That's more involved, because you'd have to create a Rect
with the appropriate width and height (depending on the view's width
and height
) to maintain the aspect ratio, and then work out the x and y coordinates to draw it so it's centred. And you'd need to recalculate that every time a new background bitmap is provided, or whenever the onSizeChanged
is called on the View
.
But passing the background bitmap into your custom view also gives you the ability to define the size of the DrawingView
itself by overriding onMeasure
, and basically constraining the View to fit the Bitmap
's aspect ratio. The problem with the other approaches is that the DrawingView
is whatever size it is, and the background is just drawn into that space filling up whatever it happens to fill.
So that means you can draw over the empty parts of the background - and that might not be a problem! But if it is, you could override onMeasure
to make use of the potential width and height your View could be, and limit one of those axes to constrain the whole View to fit your Bitmap.
That's a little involved and I don't have time to write an example, but here's an article on onMeasure
that shows you how you can control the actual size of your View, if that's a thing you want to pursue.