Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to set a rotation on SSIV #5

Merged
merged 1 commit into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
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
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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();
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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));
}
}

Expand All @@ -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;
Expand Down Expand Up @@ -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();
}

/**
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)));
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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>
Expand Down