diff --git a/gallery/build.properties b/gallery/build.properties
index cfdb87e..295cbaa 100644
--- a/gallery/build.properties
+++ b/gallery/build.properties
@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
-#Sun Apr 26 23:52:51 HKT 2026
+#Mon Apr 27 15:42:17 CST 2026
stageCount=7
libraryProject=
baseVersion=15.0
publishVersion=15.0.6
-buildCount=0
+buildCount=16
baseBetaVersion=15.0.7
diff --git a/gallery/src/main/java/cc/winboll/studio/gallery/CropActivity.java b/gallery/src/main/java/cc/winboll/studio/gallery/CropActivity.java
index 187fa09..b7ab388 100644
--- a/gallery/src/main/java/cc/winboll/studio/gallery/CropActivity.java
+++ b/gallery/src/main/java/cc/winboll/studio/gallery/CropActivity.java
@@ -5,14 +5,17 @@ import android.graphics.BitmapFactory;
import android.graphics.RectF;
import android.net.Uri;
import android.os.Bundle;
+import android.content.Intent;
import android.provider.MediaStore;
import android.view.View;
import android.widget.ImageView;
+import android.widget.ScrollView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.libappbase.LogUtils;
import java.io.File;
import java.io.FileOutputStream;
+import java.io.IOException;
public class CropActivity extends AppCompatActivity {
public static final String TAG = "CropActivity";
@@ -23,6 +26,7 @@ public class CropActivity extends AppCompatActivity {
public static final String EXTRA_CROP_HEIGHT = "crop_height";
private CropCanvasView cropCanvasView;
+ private ZoomContainerView zoomContainer;
private Bitmap originalBitmap;
private String imagePath;
private String albumPath;
@@ -73,6 +77,25 @@ public class CropActivity extends AppCompatActivity {
}
});
+ zoomContainer = findViewById(R.id.zoom_container);
+ findViewById(R.id.btn_zoom_in).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (zoomContainer != null) {
+ zoomContainer.zoomIn();
+ }
+ }
+ });
+
+ findViewById(R.id.btn_zoom_out).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (zoomContainer != null) {
+ zoomContainer.zoomOut();
+ }
+ }
+ });
+
cropCanvasView = findViewById(R.id.crop_canvas_view);
loadImage();
}
@@ -118,37 +141,57 @@ public class CropActivity extends AppCompatActivity {
}
private void saveCroppedCover() {
- Bitmap canvasBitmap = cropCanvasView.getCanvasBitmap();
- if (canvasBitmap == null) {
+ if (originalBitmap == null || originalBitmap.isRecycled()) {
Toast.makeText(this, "Failed to get image", Toast.LENGTH_SHORT).show();
return;
}
try {
RectF cropRect = cropCanvasView.getCropRect();
+ RectF imageBounds = cropCanvasView.getImageBounds();
- int cropX = (int) cropRect.left;
- int cropY = (int) cropRect.top;
- int cropW = (int) cropRect.width();
- int cropH = (int) cropRect.height();
+ float cropX = cropRect.left - imageBounds.left;
+ float cropY = cropRect.top - imageBounds.top;
+ float cropW = cropRect.width();
+ float cropH = cropRect.height();
- cropX = Math.max(0, Math.min(cropX, canvasBitmap.getWidth() - 1));
- cropY = Math.max(0, Math.min(cropY, canvasBitmap.getHeight() - 1));
- cropW = Math.max(1, Math.min(cropW, canvasBitmap.getWidth() - cropX));
- cropH = Math.max(1, Math.min(cropH, canvasBitmap.getHeight() - cropY));
+ int bmpX = (int) cropX;
+ int bmpY = (int) cropY;
+ int bmpW = (int) cropW;
+ int bmpH = (int) cropH;
- Bitmap cropped = Bitmap.createBitmap(canvasBitmap, cropX, cropY, cropW, cropH);
+ bmpX = Math.max(0, Math.min(bmpX, originalBitmap.getWidth() - 1));
+ bmpY = Math.max(0, Math.min(bmpY, originalBitmap.getHeight() - 1));
+ bmpW = Math.max(1, Math.min(bmpW, originalBitmap.getWidth() - bmpX));
+ bmpH = Math.max(1, Math.min(bmpH, originalBitmap.getHeight() - bmpY));
+
+ LogUtils.d(TAG, "saveCroppedCover: crop=(" + bmpX + "," + bmpY + "," + bmpW + "," + bmpH + ")");
+ LogUtils.d(TAG, "saveCroppedCover: original size=" + originalBitmap.getWidth() + "x" + originalBitmap.getHeight());
+
+ Bitmap cropped = Bitmap.createBitmap(originalBitmap, bmpX, bmpY, bmpW, bmpH);
+ LogUtils.d(TAG, "saveCroppedCover: cropped size=" + cropped.getWidth() + "x" + cropped.getHeight());
+
+ File coverDir = new File(getFilesDir(), "covers");
+ if (!coverDir.exists()) {
+ coverDir.mkdirs();
+ }
+ File coverFile = new File(coverDir, "cover_" + System.currentTimeMillis() + ".png");
+ LogUtils.d(TAG, "saveCroppedCover: cover file=" + coverFile.getAbsolutePath());
- File cacheDir = getCacheDir();
- File coverFile = new File(cacheDir, "cover_" + System.currentTimeMillis() + ".png");
FileOutputStream fos = new FileOutputStream(coverFile);
cropped.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.close();
+ LogUtils.d(TAG, "saveCroppedCover: file exists=" + coverFile.exists() + ", length=" + coverFile.length());
+
cropped.recycle();
AlbumCoverDbHelper coverDbHelper = AlbumCoverDbHelper.getInstance(this);
coverDbHelper.setCoverWithCrop(albumPath, imagePath, coverFile.getAbsolutePath());
+ LogUtils.d(TAG, "saveCroppedCover: cover saved to db, albumPath=" + albumPath);
+
+ Intent broadcastIntent = new Intent(Preferences.ACTION_COVER_UPDATED);
+ sendBroadcast(broadcastIntent);
Toast.makeText(this, "封面已保存", Toast.LENGTH_SHORT).show();
setResult(RESULT_OK);
diff --git a/gallery/src/main/java/cc/winboll/studio/gallery/CropCanvasView.java b/gallery/src/main/java/cc/winboll/studio/gallery/CropCanvasView.java
index 470ba3c..105f845 100644
--- a/gallery/src/main/java/cc/winboll/studio/gallery/CropCanvasView.java
+++ b/gallery/src/main/java/cc/winboll/studio/gallery/CropCanvasView.java
@@ -4,6 +4,7 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
@@ -38,12 +39,18 @@ public class CropCanvasView extends View {
private RectF imageBounds = new RectF();
private RectF canvasBounds = new RectF();
private Bitmap originalBitmap;
- private Bitmap canvasBitmap;
+ private Bitmap displayBitmap;
+ private RectF initialSpanRect;
private float initialSpan;
private int backgroundColor = Color.GREEN;
private boolean colorPickMode = false;
private float pickX, pickY;
+ private float displayScale = 1.0f;
+ private float displayOffsetX = 0f;
+ private float displayOffsetY = 0f;
+ private static final int MAX_DISPLAY_SIZE = 2048;
+
public CropCanvasView(Context context) {
super(context);
init();
@@ -74,7 +81,43 @@ public class CropCanvasView extends View {
}
public void setImageBitmap(Bitmap bitmap) {
+ if (displayBitmap != null && displayBitmap != originalBitmap) {
+ displayBitmap.recycle();
+ }
this.originalBitmap = bitmap;
+ createDisplayBitmap();
+ }
+
+ private void createDisplayBitmap() {
+ if (displayBitmap != null && displayBitmap != originalBitmap) {
+ displayBitmap.recycle();
+ displayBitmap = null;
+ }
+ if (originalBitmap == null || originalBitmap.isRecycled()) {
+ return;
+ }
+ int w = originalBitmap.getWidth();
+ int h = originalBitmap.getHeight();
+ float scale = 1.0f;
+ if (w > MAX_DISPLAY_SIZE || h > MAX_DISPLAY_SIZE) {
+ scale = Math.min((float) MAX_DISPLAY_SIZE / w, (float) MAX_DISPLAY_SIZE / h);
+ int newW = (int) (w * scale);
+ int newH = (int) (h * scale);
+ displayBitmap = Bitmap.createScaledBitmap(originalBitmap, newW, newH, true);
+ } else {
+ displayBitmap = originalBitmap;
+ }
+ displayBitmapScale = scale;
+ }
+
+ private float displayBitmapScale = 1.0f;
+
+ public Bitmap getOriginalBitmap() {
+ return originalBitmap;
+ }
+
+ public float getDisplayBitmapScale() {
+ return displayBitmapScale;
}
public void initCanvas(int imgWidth, int imgHeight, float ratio) {
@@ -102,31 +145,61 @@ public class CropCanvasView extends View {
cropRect = new RectF(0, 0, canvasWidth, canvasHeight);
- createCanvasBitmap();
-
requestLayout();
invalidate();
}
- private void createCanvasBitmap() {
- if (canvasBitmap != null) {
- canvasBitmap.recycle();
- }
- canvasBitmap = Bitmap.createBitmap(canvasWidth, canvasHeight, Bitmap.Config.ARGB_8888);
- android.graphics.Canvas canvas = new android.graphics.Canvas(canvasBitmap);
- canvas.drawColor(backgroundColor);
- if (originalBitmap != null && !originalBitmap.isRecycled()) {
- canvas.drawBitmap(originalBitmap, imageBounds.left, imageBounds.top, imagePaint);
+ public void getDisplayMatrix(Matrix matrix) {
+ if (canvasWidth <= 0 || canvasHeight <= 0 || getWidth() <= 0 || getHeight() <= 0) {
+ return;
}
+ matrix.reset();
+
+ float displayScaleX = (float) getWidth() / canvasWidth;
+ float displayScaleY = (float) getHeight() / canvasHeight;
+ displayScale = Math.min(displayScaleX, displayScaleY);
+
+ displayOffsetX = (getWidth() - canvasWidth * displayScale) / 2f;
+ displayOffsetY = (getHeight() - canvasHeight * displayScale) / 2f;
+
+ matrix.postTranslate(displayOffsetX, displayOffsetY);
+ matrix.postScale(displayScale, displayScale);
}
- public Bitmap getCanvasBitmap() {
- return canvasBitmap;
+ public float screenToImageX(float screenX) {
+ return (screenX - displayOffsetX) / displayScale;
+ }
+
+ public float screenToImageY(float screenY) {
+ return (screenY - displayOffsetY) / displayScale;
+ }
+
+ public float imageToScreenX(float imageX) {
+ return imageX * displayScale + displayOffsetX;
+ }
+
+ public float imageToScreenY(float imageY) {
+ return imageY * displayScale + displayOffsetY;
+ }
+
+ public int getImageColorAt(float screenX, float screenY) {
+ Bitmap bmp = (displayBitmap != null) ? displayBitmap : originalBitmap;
+ if (bmp == null || bmp.isRecycled()) {
+ return Color.TRANSPARENT;
+ }
+ float imgX = screenToImageX(screenX);
+ float imgY = screenToImageY(screenY);
+ int bmpX = (int) ((imgX - imageBounds.left) * displayBitmapScale);
+ int bmpY = (int) ((imgY - imageBounds.top) * displayBitmapScale);
+ if (bmpX >= 0 && bmpX < bmp.getWidth() &&
+ bmpY >= 0 && bmpY < bmp.getHeight()) {
+ return bmp.getPixel(bmpX, bmpY);
+ }
+ return Color.TRANSPARENT;
}
public void setBackgroundColor(int color) {
this.backgroundColor = color;
- createCanvasBitmap();
invalidate();
}
@@ -148,30 +221,8 @@ public class CropCanvasView extends View {
}
public void scaleToView(int viewWidth, int viewHeight) {
- if (viewWidth > 0 && viewHeight > 0 && canvasWidth > 0 && canvasHeight > 0) {
- float scale = Math.max((float) viewWidth / canvasWidth, (float) viewHeight / canvasHeight);
- int oldCanvasW = canvasWidth;
- int oldCanvasH = canvasHeight;
- canvasWidth = (int) (canvasWidth * scale);
- canvasHeight = (int) (canvasHeight * scale);
-
- float left = (canvasWidth - imageWidth) / 2f;
- float top = (canvasHeight - imageHeight) / 2f;
- imageBounds.set(left, top, left + imageWidth, top + imageHeight);
- canvasBounds.set(0, 0, canvasWidth, canvasHeight);
-
- if (cropRect != null) {
- float scaleX = (float) canvasWidth / oldCanvasW;
- float scaleY = (float) canvasHeight / oldCanvasH;
- cropRect.left *= scaleX;
- cropRect.top *= scaleY;
- cropRect.right *= scaleX;
- cropRect.bottom *= scaleY;
- }
-
- requestLayout();
- invalidate();
- }
+ requestLayout();
+ invalidate();
}
public RectF getCropRect() {
@@ -194,6 +245,14 @@ public class CropCanvasView extends View {
return new RectF(imageBounds);
}
+ public float getBitmapScale() {
+ return displayScale;
+ }
+
+ public float getDisplayScale() {
+ return displayScale;
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int w = canvasWidth > 0 ? canvasWidth : 0;
@@ -201,15 +260,51 @@ public class CropCanvasView extends View {
setMeasuredDimension(w, h);
}
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ getDisplayMatrix(new Matrix());
+ }
+
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
- if (canvasBitmap != null && !canvasBitmap.isRecycled()) {
- canvas.drawBitmap(canvasBitmap, 0, 0, null);
- }
+ if (cropRect == null) return;
- if (cropRect != null) {
+ if (displayBitmap != null && !displayBitmap.isRecycled()) {
+ canvas.save();
+
+ Matrix matrix = new Matrix();
+ getDisplayMatrix(matrix);
+ canvas.concat(matrix);
+
+ canvas.drawColor(backgroundColor);
+
+ if (displayBitmap == originalBitmap) {
+ canvas.drawBitmap(displayBitmap, imageBounds.left, imageBounds.top, imagePaint);
+ } else {
+ float invScale = 1f / displayBitmapScale;
+ canvas.save();
+ canvas.scale(invScale, invScale);
+ canvas.drawBitmap(displayBitmap, imageBounds.left * displayBitmapScale,
+ imageBounds.top * displayBitmapScale, imagePaint);
+ canvas.restore();
+ }
+
+ canvas.restore();
+
+ canvas.drawRect(cropRect, borderPaint);
+
+ float scX = imageToScreenX(cropRect.left);
+ float scY = imageToScreenY(cropRect.top);
+ float scR = imageToScreenX(cropRect.right);
+ float scB = imageToScreenY(cropRect.bottom);
+ canvas.drawCircle(scX, scY, 12, cornerPaint);
+ canvas.drawCircle(scR, scY, 12, cornerPaint);
+ canvas.drawCircle(scX, scB, 12, cornerPaint);
+ canvas.drawCircle(scR, scB, 12, cornerPaint);
+ } else {
canvas.drawRect(cropRect, borderPaint);
canvas.drawCircle(cropRect.left, cropRect.top, 12, cornerPaint);
@@ -387,14 +482,6 @@ public class CropCanvasView extends View {
}
public int getColorAt(float x, float y) {
- if (canvasBitmap != null && !canvasBitmap.isRecycled()) {
- int bitmapX = (int) x;
- int bitmapY = (int) y;
- if (bitmapX >= 0 && bitmapX < canvasBitmap.getWidth() &&
- bitmapY >= 0 && bitmapY < canvasBitmap.getHeight()) {
- return canvasBitmap.getPixel(bitmapX, bitmapY);
- }
- }
- return Color.TRANSPARENT;
+ return getImageColorAt(x, y);
}
}
\ No newline at end of file
diff --git a/gallery/src/main/java/cc/winboll/studio/gallery/MainActivity.java b/gallery/src/main/java/cc/winboll/studio/gallery/MainActivity.java
index 3cfabaf..a90e6dc 100644
--- a/gallery/src/main/java/cc/winboll/studio/gallery/MainActivity.java
+++ b/gallery/src/main/java/cc/winboll/studio/gallery/MainActivity.java
@@ -1,8 +1,11 @@
package cc.winboll.studio.gallery;
import android.Manifest;
+import android.content.BroadcastReceiver;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.media.MediaScannerConnection;
@@ -192,13 +195,16 @@ private void loadAlbums() {
LogUtils.d(TAG, "scanning folder: " + subfolder.getName());
String albumPath = subfolder.getAbsolutePath();
String coverPath = coverDbHelper.getCover(albumPath);
+ LogUtils.d(TAG, "loadAlbums: album=" + albumPath + ", coverPath=" + coverPath);
Uri coverUri = null;
if (coverPath != null) {
File coverFile = new File(coverPath);
if (coverFile.exists()) {
coverUri = Uri.fromFile(coverFile);
+ LogUtils.d(TAG, "loadAlbums: cover from file=" + coverFile.getAbsolutePath());
} else {
coverUri = getUriFromPath(coverPath);
+ LogUtils.d(TAG, "loadAlbums: cover from media store path=" + coverPath);
}
}
if (coverUri == null) {
@@ -299,9 +305,19 @@ private void loadAlbums() {
return super.onOptionsItemSelected(item);
}
+ private BroadcastReceiver coverUpdatedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Preferences.ACTION_COVER_UPDATED.equals(intent.getAction())) {
+ loadAlbums();
+ }
+ }
+ };
+
@Override
protected void onResume() {
super.onResume();
+ registerReceiver(coverUpdatedReceiver, new IntentFilter(Preferences.ACTION_COVER_UPDATED));
if (checkPermission()) {
scanMediaStore();
loadAlbums();
@@ -313,6 +329,12 @@ private void loadAlbums() {
}
}
+ @Override
+ protected void onPause() {
+ super.onPause();
+ unregisterReceiver(coverUpdatedReceiver);
+ }
+
private void scanMediaStore() {
String folderPath = prefs.getFolderPath();
File baseFolder = new File(folderPath);
diff --git a/gallery/src/main/java/cc/winboll/studio/gallery/Preferences.java b/gallery/src/main/java/cc/winboll/studio/gallery/Preferences.java
index 5746194..d005cd9 100644
--- a/gallery/src/main/java/cc/winboll/studio/gallery/Preferences.java
+++ b/gallery/src/main/java/cc/winboll/studio/gallery/Preferences.java
@@ -13,6 +13,9 @@ public class Preferences {
private static final String KEY_COVER_WIDTH = "cover_width";
private static final String KEY_COVER_HEIGHT = "cover_height";
private static final String KEY_COVER_RATIO = "cover_ratio";
+
+ public static final String ACTION_COVER_UPDATED = "cc.winboll.studio.gallery.COVER_UPDATED";
+
private static final int DEFAULT_BG_TYPE = 0;
private static final int DEFAULT_SORT_MODE = 0;
private static final int DEFAULT_COVER_WIDTH = 240;
diff --git a/gallery/src/main/java/cc/winboll/studio/gallery/ZoomContainerView.java b/gallery/src/main/java/cc/winboll/studio/gallery/ZoomContainerView.java
new file mode 100644
index 0000000..86ab5c1
--- /dev/null
+++ b/gallery/src/main/java/cc/winboll/studio/gallery/ZoomContainerView.java
@@ -0,0 +1,124 @@
+package cc.winboll.studio.gallery;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.View;
+import android.widget.FrameLayout;
+
+public class ZoomContainerView extends FrameLayout {
+ private float scaleFactor = 1.0f;
+ private float minScale = 0.5f;
+ private float maxScale = 5.0f;
+ private static final float ZOOM_STEP = 0.25f;
+
+ private ScaleGestureDetector scaleGestureDetector;
+ private GestureDetector gestureDetector;
+ private Paint borderPaint;
+
+ public ZoomContainerView(Context context) {
+ super(context);
+ init();
+ }
+
+ public ZoomContainerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public ZoomContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ setWillNotDraw(false);
+
+ borderPaint = new Paint();
+ borderPaint.setColor(Color.parseColor("#333333"));
+ borderPaint.setStyle(Paint.Style.STROKE);
+ borderPaint.setStrokeWidth(2);
+
+ scaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() {
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ scaleFactor *= detector.getScaleFactor();
+ scaleFactor = Math.max(minScale, Math.min(scaleFactor, maxScale));
+ invalidate();
+ requestLayout();
+ return true;
+ }
+ });
+
+ gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ if (scaleFactor > 1.5f) {
+ scaleFactor = 1.0f;
+ } else {
+ scaleFactor = 2.0f;
+ }
+ invalidate();
+ requestLayout();
+ return true;
+ }
+ });
+ }
+
+ public void zoomIn() {
+ scaleFactor = Math.min(maxScale, scaleFactor + ZOOM_STEP);
+ invalidate();
+ requestLayout();
+ }
+
+ public void zoomOut() {
+ scaleFactor = Math.max(minScale, scaleFactor - ZOOM_STEP);
+ invalidate();
+ requestLayout();
+ }
+
+ public float getScaleFactor() {
+ return scaleFactor;
+ }
+
+ public void resetZoom() {
+ scaleFactor = 1.0f;
+ invalidate();
+ requestLayout();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ int childCount = getChildCount();
+ if (childCount > 0) {
+ View child = getChildAt(0);
+ measureChild(child, widthMeasureSpec, heightMeasureSpec);
+ int childW = (int) (child.getMeasuredWidth() * scaleFactor);
+ int childH = (int) (child.getMeasuredHeight() * scaleFactor);
+ setMeasuredDimension(
+ MeasureSpec.getSize(widthMeasureSpec),
+ MeasureSpec.getSize(heightMeasureSpec)
+ );
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean handled = scaleGestureDetector.onTouchEvent(event);
+ if (!scaleGestureDetector.isInProgress()) {
+ handled = gestureDetector.onTouchEvent(event) || handled;
+ }
+ return handled || super.onTouchEvent(event);
+ }
+}
\ No newline at end of file
diff --git a/gallery/src/main/res/drawable/ic_done.xml b/gallery/src/main/res/drawable/ic_done.xml
new file mode 100644
index 0000000..a7672f7
--- /dev/null
+++ b/gallery/src/main/res/drawable/ic_done.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/gallery/src/main/res/drawable/ic_zoom_in.xml b/gallery/src/main/res/drawable/ic_zoom_in.xml
new file mode 100644
index 0000000..45a16f6
--- /dev/null
+++ b/gallery/src/main/res/drawable/ic_zoom_in.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/gallery/src/main/res/drawable/ic_zoom_out.xml b/gallery/src/main/res/drawable/ic_zoom_out.xml
new file mode 100644
index 0000000..6fdbb4a
--- /dev/null
+++ b/gallery/src/main/res/drawable/ic_zoom_out.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/gallery/src/main/res/layout/activity_crop.xml b/gallery/src/main/res/layout/activity_crop.xml
index e90e7df..f7456e8 100644
--- a/gallery/src/main/res/layout/activity_crop.xml
+++ b/gallery/src/main/res/layout/activity_crop.xml
@@ -34,23 +34,54 @@
android:layout_height="0dp"
android:layout_weight="1"/>
-
+
+
+
+
-
+ android:layout_marginTop="56dp"
+ android:layout_marginBottom="56dp"
+ android:fillViewport="true">
+
+
+
+
+
+
+
+
\ No newline at end of file