Skip to content
This repository has been archived by the owner on Jan 29, 2025. It is now read-only.

Add ability to set a rotation on SSIV #5

Merged
merged 1 commit into from
Aug 23, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.davemorrissey.labs.subscaleview

enum class ImageRotation(val rotation: Int) {
ROTATION_0(0), ROTATION_90(90), ROTATION_180(180), ROTATION_270(270);

fun rotateBy90Degrees(): ImageRotation = when (this) {
ROTATION_0 -> ROTATION_90
ROTATION_90 -> ROTATION_180
ROTATION_180 -> ROTATION_270
ROTATION_270 -> ROTATION_0
}
}
Original file line number Diff line number Diff line change
@@ -206,6 +206,7 @@ public class SubsamplingScaleImageView extends View {
// Source image dimensions and orientation - dimensions relate to the unrotated image
private int sWidth;
private int sHeight;
private ImageRotation imageRotation = ImageRotation.ROTATION_0;
// Min scale allowed (prevent infinite zoom)
private float minScale = minScale();
private Rect sRegion;
@@ -536,12 +537,12 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = parentHeight;
if (sWidth > 0 && sHeight > 0) {
if (resizeWidth && resizeHeight) {
width = sWidth;
height = sHeight;
width = getEffectiveSWidth();
height = getEffectiveSHeight();
} else if (resizeHeight) {
height = (int) ((((double) sHeight / (double) sWidth) * width));
height = (int) ((((double) getEffectiveSHeight() / (double) getEffectiveSWidth()) * width));
} else if (resizeWidth) {
width = (int) ((((double) sWidth / (double) sHeight) * height));
width = (int) ((((double) getEffectiveSWidth() / (double) getEffectiveSHeight()) * height));
}
}
width = Math.max(width, getSuggestedMinimumWidth());
@@ -606,6 +607,8 @@ public boolean onTouchEvent(@NonNull MotionEvent event) {
@SuppressWarnings("deprecation")
private boolean onTouchEventInternal(@NonNull MotionEvent event) {
int touchCount = event.getPointerCount();
int sHeight = getEffectiveSHeight();
int sWidth = getEffectiveSWidth();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_1_DOWN:
@@ -851,8 +854,8 @@ private void doubleTapZoom(PointF sCenter, PointF vFocus) {
sCenter.y = sRequestedCenter.y;
} else {
// With no requested center, scale around the image center.
sCenter.x = sWidth / 2;
sCenter.y = sHeight / 2;
sCenter.x = getEffectiveSWidth() / 2;
sCenter.y = getEffectiveSHeight() / 2;
}
}
float doubleTapZoomScale = Math.min(maxScale, SubsamplingScaleImageView.this.doubleTapZoomScale);
@@ -967,7 +970,17 @@ protected void onDraw(Canvas canvas) {
}
matrix.reset();
setMatrixArray(srcArray, 0, 0, tile.bitmap.getWidth(), 0, tile.bitmap.getWidth(), tile.bitmap.getHeight(), 0, tile.bitmap.getHeight());
setMatrixArray(dstArray, tile.vRect.left, tile.vRect.top, tile.vRect.right, tile.vRect.top, tile.vRect.right, tile.vRect.bottom, tile.vRect.left, tile.vRect.bottom);

switch (getImageRotation()) {
case ROTATION_0 ->
setMatrixArray(dstArray, tile.vRect.left, tile.vRect.top, tile.vRect.right, tile.vRect.top, tile.vRect.right, tile.vRect.bottom, tile.vRect.left, tile.vRect.bottom);
case ROTATION_90 ->
setMatrixArray(dstArray, tile.vRect.right, tile.vRect.top, tile.vRect.right, tile.vRect.bottom, tile.vRect.left, tile.vRect.bottom, tile.vRect.left, tile.vRect.top);
case ROTATION_180 ->
setMatrixArray(dstArray, tile.vRect.right, tile.vRect.bottom, tile.vRect.left, tile.vRect.bottom, tile.vRect.left, tile.vRect.top, tile.vRect.right, tile.vRect.top);
case ROTATION_270 ->
setMatrixArray(dstArray, tile.vRect.left, tile.vRect.bottom, tile.vRect.left, tile.vRect.top, tile.vRect.right, tile.vRect.top, tile.vRect.right, tile.vRect.bottom);
}
matrix.setPolyToPoly(srcArray, 0, dstArray, 0, 4);
canvas.drawBitmap(tile.bitmap, matrix, bitmapPaint);
if (debug) {
@@ -992,8 +1005,15 @@ protected void onDraw(Canvas canvas) {
}
matrix.reset();
matrix.postScale(xScale, yScale);
matrix.postRotate(getImageRotation().getRotation());
matrix.postTranslate(vTranslate.x, vTranslate.y);

switch (getImageRotation()) {
case ROTATION_90 -> matrix.postTranslate(scale * sHeight, 0);
case ROTATION_180 -> matrix.postTranslate(scale * sWidth, scale * sHeight);
case ROTATION_270 -> matrix.postTranslate(0, scale * sWidth);
}

if (tileBgPaint != null) {
if (sRect == null) {
sRect = new RectF();
@@ -1259,6 +1279,9 @@ private int calculateInSampleSize(float scale) {
scale = (minimumTileDpi / averageDpi) * scale;
}

int sWidth = getEffectiveSWidth();
int sHeight = getEffectiveSHeight();

int reqWidth = (int) (sWidth * scale);
int reqHeight = (int) (sHeight * scale);

@@ -1304,8 +1327,8 @@ private void fitToBounds(boolean center, ScaleAndTranslate sat) {

PointF vTranslate = sat.vTranslate;
float scale = limitedScale(sat.scale);
float scaleWidth = scale * sWidth;
float scaleHeight = scale * sHeight;
float scaleWidth = scale * getEffectiveSWidth();
float scaleHeight = scale * getEffectiveSHeight();

boolean extra = panLimit == PAN_LIMIT_INSIDE;
float extraLeft = extra ? vExtraSpaceLeft : 0;
@@ -1368,7 +1391,7 @@ private void fitToBounds(boolean center) {
scale = satTemp.scale;
vTranslate.set(satTemp.vTranslate);
if (init) {
vTranslate.set(vTranslateForSCenter(sWidth / 2, sHeight / 2, scale));
vTranslate.set(vTranslateForSCenter((float) getEffectiveSWidth() / 2, (float) getEffectiveSHeight() / 2, scale));
}
}

@@ -1381,6 +1404,8 @@ private void initialiseTileMap(Point maxTileDimensions) {
int sampleSize = fullImageSampleSize;
int xTiles = 1;
int yTiles = 1;
int sWidth = getEffectiveSWidth();
int sHeight = getEffectiveSHeight();
while (true) {
int sTileWidth = sWidth / xTiles;
int sTileHeight = sHeight / yTiles;
@@ -1545,13 +1570,63 @@ private Point getMaxBitmapDimensions(Canvas canvas) {
return new Point(Math.min(canvas.getMaximumBitmapWidth(), maxTileWidth), Math.min(canvas.getMaximumBitmapHeight(), maxTileHeight));
}

/**
* Get source width taking rotation into account.
*/
@SuppressWarnings("SuspiciousNameCombination")
private int getEffectiveSWidth() {
ImageRotation rotation = getImageRotation();
if (rotation == ImageRotation.ROTATION_90 || rotation == ImageRotation.ROTATION_270) {
return sHeight;
} else {
return sWidth;
}
}

/**
* Get source height taking rotation into account.
*/
@SuppressWarnings("SuspiciousNameCombination")
private int getEffectiveSHeight() {
ImageRotation rotation = getImageRotation();
if (rotation == ImageRotation.ROTATION_90 || rotation == ImageRotation.ROTATION_270) {
return sWidth;
} else {
return sHeight;
}
}

/**
* Converts source rectangle from tile, which treats the image file as if it were in the correct orientation already,
* to the rectangle of the image that needs to be loaded.
*/
@SuppressWarnings("SuspiciousNameCombination")
@AnyThread
private void fileSRect(Rect sRect, Rect target) {
target.set(sRect);
ImageRotation rotation = getImageRotation();

switch (rotation) {
case ROTATION_0 ->
target.set(sRect);
case ROTATION_90 ->
target.set(sRect.top, sHeight - sRect.right, sRect.bottom, sHeight - sRect.left);
case ROTATION_180 ->
target.set(sWidth - sRect.right, sHeight - sRect.bottom, sWidth - sRect.left, sHeight - sRect.top);
case ROTATION_270 ->
target.set(sWidth - sRect.bottom, sRect.left, sWidth - sRect.top, sRect.right);
}
}

public ImageRotation getImageRotation() {
return imageRotation;
}

public void setImageRotation(ImageRotation rotation) {
this.imageRotation = rotation;

reset(false);
invalidate();
requestLayout();
}

/**
@@ -1825,6 +1900,8 @@ private float minScale() {

int vPadding = getPaddingBottom() + getPaddingTop() + vExtra;
int hPadding = getPaddingLeft() + getPaddingRight() + hExtra;
int sWidth = getEffectiveSWidth();
int sHeight = getEffectiveSHeight();
switch (minimumScaleType) {
case SCALE_TYPE_CENTER_INSIDE:
default:
@@ -1939,8 +2016,8 @@ public final void getPanRemaining(RectF vTarget) {
return;
}

float scaleWidth = scale * sWidth;
float scaleHeight = scale * sHeight;
float scaleWidth = scale * getEffectiveSWidth();
float scaleHeight = scale * getEffectiveSHeight();

if (panLimit == PAN_LIMIT_CENTER) {
vTarget.top = Math.max(0, -(vTranslate.y - (getHeight() / 2)));
@@ -2215,7 +2292,7 @@ public final void resetScaleAndCenter() {
this.anim = null;
this.pendingScale = limitedScale(0);
if (isReady()) {
this.sPendingCenter = new PointF(sWidth / 2, sHeight / 2);
this.sPendingCenter = new PointF(getEffectiveSWidth() / 2, getEffectiveSHeight() / 2);
} else {
this.sPendingCenter = new PointF(0, 0);
}
@@ -2348,8 +2425,8 @@ public final boolean isPanEnabled() {
public final void setPanEnabled(boolean panEnabled) {
this.panEnabled = panEnabled;
if (!panEnabled && vTranslate != null) {
vTranslate.x = (getWidth() / 2) - (scale * (sWidth / 2));
vTranslate.y = (getHeight() / 2) - (scale * (sHeight / 2));
vTranslate.x = (getWidth() / 2) - (scale * (getEffectiveSWidth() / 2));
vTranslate.y = (getHeight() / 2) - (scale * (getEffectiveSHeight() / 2));
if (isReady()) {
refreshRequiredTiles(true);
invalidate();
@@ -2745,6 +2822,11 @@ protected Bitmap doInBackground(Void... params) {
view.decoderLock.readLock().lock();
try {
if (decoder.isReady()) {
// Update tile's file sRect according to rotation
view.fileSRect(tile.sRect, tile.fileSRect);
if (view.sRegion != null) {
tile.fileSRect.offset(view.sRegion.left, view.sRegion.top);
}
return decoder.decodeRegion(tile.fileSRect, tile.sampleSize);
} else {
tile.loading = false;
Original file line number Diff line number Diff line change
@@ -27,6 +27,10 @@ class ImageDisplayRotateFragment : Fragment() {
ImageSource.asset(requireContext(), "swissroad.jpg")
)

binding.rotate.setOnClickListener {
binding.imageView.imageRotation = binding.imageView.imageRotation.rotateBy90Degrees()
}

return binding.root
}
}
2 changes: 1 addition & 1 deletion sample/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@
</string>
<string name="display.p2.subtitle">Rotation</string>
<string name="display.p2.text">
This image has been rotated 90 degrees. Tap the button to rotate it. EXIF rotation is supported for external files.
This image can be rotated by 90 degree increments. Tap the button to do so. EXIF data is not parsed, do it yourself!
</string>
<string name="display.p3.subtitle">Display region</string>
<string name="display.p3.text">Set the region to display instead of the whole image.</string>