Skip to content

Commit

Permalink
feat: add "scalingMode" property to Ti.UI.ImageView (#12753)
Browse files Browse the repository at this point in the history
Fixes TIMOB-24313 TIMOB-28432
  • Loading branch information
jquick-axway authored Aug 24, 2021
1 parent cde4064 commit cce763a
Show file tree
Hide file tree
Showing 14 changed files with 465 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,17 @@ public class MediaModule extends KrollModule implements Handler.Callback
@Kroll.constant
public static final int NO_VIDEO = 3;

@Kroll.constant
public static final int IMAGE_SCALING_AUTO = -1;
@Kroll.constant
public static final int IMAGE_SCALING_NONE = 0;
@Kroll.constant
public static final int IMAGE_SCALING_FILL = 1;
@Kroll.constant
public static final int IMAGE_SCALING_ASPECT_FILL = 2;
@Kroll.constant
public static final int IMAGE_SCALING_ASPECT_FIT = 3;

@Kroll.constant
public static final int VIDEO_SCALING_NONE = 0;
@Kroll.constant
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
/**
* Appcelerator Titanium Mobile
* Copyright (c) 2009-2016 by Appcelerator, Inc. All Rights Reserved.
* Copyright (c) 2009-2021 by Axway, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
package ti.modules.titanium.ui;

import android.app.Activity;
import org.appcelerator.kroll.annotations.Kroll;
import org.appcelerator.titanium.TiBlob;
import org.appcelerator.titanium.TiC;
import org.appcelerator.titanium.view.TiUIView;

import ti.modules.titanium.media.MediaModule;
import ti.modules.titanium.ui.widget.TiUIImageView;
import android.app.Activity;

@Kroll.proxy(creatableInModule = UIModule.class,
propertyAccessors = {
Expand All @@ -25,13 +25,16 @@
TiC.PROPERTY_IMAGE_TOUCH_FEEDBACK,
TiC.PROPERTY_IMAGE_TOUCH_FEEDBACK_COLOR,
TiC.PROPERTY_IMAGES,
TiC.PROPERTY_REPEAT_COUNT
TiC.PROPERTY_REPEAT_COUNT,
TiC.PROPERTY_SCALING_MODE
})
public class ImageViewProxy extends ViewProxy
{
public ImageViewProxy()
{
super();
defaultValues.put(TiC.PROPERTY_AUTOROTATE, true);
defaultValues.put(TiC.PROPERTY_SCALING_MODE, MediaModule.IMAGE_SCALING_AUTO);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
/**
* Appcelerator Titanium Mobile
* Copyright (c) 2009-2013 by Appcelerator, Inc. All Rights Reserved.
* Copyright (c) 2009-2021 by Axway, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
package ti.modules.titanium.ui.widget;

import java.lang.ref.WeakReference;

import org.appcelerator.kroll.common.Log;
import org.appcelerator.titanium.proxy.TiViewProxy;
import org.appcelerator.titanium.util.TiUIHelper;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
Expand All @@ -35,8 +28,13 @@
import android.widget.ZoomControls;
import android.graphics.PorterDuff.Mode;
import com.google.android.material.color.MaterialColors;
import java.lang.ref.WeakReference;
import org.appcelerator.titanium.proxy.TiViewProxy;
import org.appcelerator.titanium.R;
import org.appcelerator.titanium.util.TiExifOrientation;
import org.appcelerator.titanium.util.TiUIHelper;
import org.appcelerator.titanium.util.TiColorHelper;
import ti.modules.titanium.media.MediaModule;

public class TiImageView extends ViewGroup implements Handler.Callback, OnClickListener
{
Expand All @@ -60,6 +58,7 @@ public class TiImageView extends ViewGroup implements Handler.Callback, OnClickL
private ImageView imageView;
private ZoomControls zoomControls;

private int scalingMode = MediaModule.IMAGE_SCALING_AUTO;
private float scaleFactor;
private float scaleIncrement;
private float scaleMin;
Expand All @@ -72,7 +71,7 @@ public class TiImageView extends ViewGroup implements Handler.Callback, OnClickL
private boolean viewWidthDefined;
private boolean viewHeightDefined;

private int orientation;
private TiExifOrientation exifOrientation = TiExifOrientation.UPRIGHT;
private int tintColor;
private WeakReference<TiViewProxy> proxy;

Expand All @@ -89,7 +88,6 @@ public TiImageView(Context context)
scaleIncrement = 0.1f;
scaleMin = 1.0f;
scaleMax = 5.0f;
orientation = 0;

baseMatrix = new Matrix();
changeMatrix = new Matrix();
Expand All @@ -98,6 +96,8 @@ public TiImageView(Context context)
this.imageRippleColor = defaultRippleColor;

imageView = new ImageView(context);
imageView.setAdjustViewBounds(true);
imageView.setScaleType(ScaleType.MATRIX);
addView(imageView);
setEnableScale(true);

Expand Down Expand Up @@ -175,7 +175,7 @@ public TiImageView(Context context, TiViewProxy proxy)
public void setEnableScale(boolean enableScale)
{
this.enableScale = enableScale;
updateScaleType();
requestLayout();
}

public void setEnableZoomControls(boolean enableZoomControls)
Expand All @@ -184,6 +184,14 @@ public void setEnableZoomControls(boolean enableZoomControls)
updateScaleType();
}

public void setScalingMode(int mode)
{
if (mode != this.scalingMode) {
this.scalingMode = mode;
requestLayout();
}
}

public boolean isImageRippleEnabled()
{
return this.isImageRippleEnabled;
Expand Down Expand Up @@ -348,56 +356,83 @@ private void onViewChanged(float dscale)

private void computeBaseMatrix()
{
Drawable d = imageView.getDrawable();
baseMatrix.reset();

if (d != null) {
// The base matrix is the matrix that displays the entire image bitmap.
// It orients the image when orientation is set and scales in X and Y independently,
// so that src matches dst exactly.
// This may change the aspect ratio of the src.
Rect r = new Rect();
getDrawingRect(r);
int intrinsicWidth = d.getIntrinsicWidth();
int intrinsicHeight = d.getIntrinsicHeight();
int dwidth = intrinsicWidth;
int dheight = intrinsicHeight;

if (orientation > 0) {
baseMatrix.postRotate(orientation);
if (orientation == 90 || orientation == 270) {
dwidth = intrinsicHeight;
dheight = intrinsicWidth;
}
}
// Reset base matrix used to apply "scalingMode" and EXIF orientation to image,
// but does not have "zoom" scaling/panning applied to it.
this.baseMatrix.reset();

float vwidth = getWidth() - getPaddingLeft() - getPaddingRight();
float vheight = getHeight() - getPaddingTop() - getPaddingBottom();

RectF dRectF = null;
RectF vRectF = new RectF(0, 0, vwidth, vheight);
if (orientation == 0) {
dRectF = new RectF(0, 0, dwidth, dheight);
} else if (orientation == 90) {
dRectF = new RectF(-dwidth, 0, 0, dheight);
} else if (orientation == 180) {
dRectF = new RectF(-dwidth, -dheight, 0, 0);
} else if (orientation == 270) {
dRectF = new RectF(0, -dheight, dwidth, 0);
} else {
Log.e(TAG, "Invalid value for orientation. Cannot compute the base matrix for the image.");
return;
// Fetch the image drawable.
Drawable drawable = imageView.getDrawable();
if (drawable == null) {
return;
}

// Fetch the image's pixel dimensions.
boolean isUpright = !this.exifOrientation.isSideways();
float imageWidth = isUpright ? drawable.getIntrinsicWidth() : drawable.getIntrinsicHeight();
float imageHeight = isUpright ? drawable.getIntrinsicHeight() : drawable.getIntrinsicWidth();

// Rotate image to upright position and undo mirroring if needed.
if (this.exifOrientation != TiExifOrientation.UPRIGHT) {
this.baseMatrix.postRotate(this.exifOrientation.getDegreesCounterClockwise());
switch (this.exifOrientation.getDegreesCounterClockwise()) {
case 90:
this.baseMatrix.postTranslate(imageWidth, 0);
break;
case 180:
this.baseMatrix.postTranslate(imageWidth, imageHeight);
break;
case 270:
this.baseMatrix.postTranslate(0, imageHeight);
break;
}
if (this.exifOrientation.isMirrored()) {
this.baseMatrix.postScale(-1.0f, 1.0f, imageWidth * 0.5f, 0.0f);
}
}

Matrix m = new Matrix();
Matrix.ScaleToFit scaleType;
if (viewWidthDefined && viewHeightDefined) {
scaleType = Matrix.ScaleToFit.FILL;
// Fetch the pixel width/height of the view hosting the image.
float viewWidth = getWidth() - getPaddingLeft() - getPaddingRight();
float viewHeight = getHeight() - getPaddingTop() - getPaddingBottom();

// Calculate scale factors needed to stretch image to fit width/height of view.
float scaleX = 0.0f;
float scaleY = 0.0f;
if (imageWidth > 0) {
scaleX = viewWidth / imageWidth;
}
if (imageHeight > 0) {
scaleY = viewHeight / imageHeight;
}

// Apply "scalingMode" to image.
int scalingMode = this.scalingMode;
if (scalingMode == MediaModule.IMAGE_SCALING_AUTO) {
if (!this.enableZoomControls && !this.enableScale && this.viewWidthDefined && this.viewHeightDefined) {
scalingMode = MediaModule.IMAGE_SCALING_FILL;
} else {
scaleType = Matrix.ScaleToFit.CENTER;
scalingMode = MediaModule.IMAGE_SCALING_ASPECT_FIT;
}
}
switch (scalingMode) {
case MediaModule.IMAGE_SCALING_ASPECT_FILL:
case MediaModule.IMAGE_SCALING_ASPECT_FIT: {
// Crop or letterbox/pillar-box scale the image.
boolean isCropped = (scalingMode == MediaModule.IMAGE_SCALING_ASPECT_FILL);
float scale = isCropped ? Math.max(scaleX, scaleY) : Math.min(scaleX, scaleY);
this.baseMatrix.postScale(scale, scale);
this.baseMatrix.postTranslate(
(viewWidth - (imageWidth * scale)) * 0.5f, (viewHeight - (imageHeight * scale)) * 0.5f);
break;
}
m.setRectToRect(dRectF, vRectF, scaleType);
baseMatrix.postConcat(m);
case MediaModule.IMAGE_SCALING_FILL:
// Stretch the image to fit container.
this.baseMatrix.postScale(scaleX, scaleY);
break;
case MediaModule.IMAGE_SCALING_NONE:
default:
// Do not scale the image.
this.baseMatrix.postTranslate((viewWidth - imageWidth) * 0.5f, (viewHeight - imageHeight) * 0.5f);
break;
}
}

Expand Down Expand Up @@ -444,6 +479,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);

boolean isUpright = !this.exifOrientation.isSideways();
int maxWidth = 0;
int maxHeight = 0;

Expand All @@ -452,33 +488,49 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
if (!viewWidthDefined || !viewHeightDefined) {
Drawable d = imageView.getDrawable();
if (d != null) {
float aspectRatio = 1;
float aspectRatio = 1.0f;
int w = MeasureSpec.getSize(widthMeasureSpec);
int h = MeasureSpec.getSize(heightMeasureSpec);

int ih = d.getIntrinsicHeight();
int iw = d.getIntrinsicWidth();
int ih = isUpright ? d.getIntrinsicHeight() : d.getIntrinsicWidth();
int iw = isUpright ? d.getIntrinsicWidth() : d.getIntrinsicHeight();
if (ih != 0 && iw != 0) {
aspectRatio = 1f * ih / iw;
aspectRatio = (float) ih / (float) iw;
}

switch (this.scalingMode) {
case MediaModule.IMAGE_SCALING_NONE:
case MediaModule.IMAGE_SCALING_ASPECT_FILL:
maxWidth = iw;
maxHeight = ih;
break;
}

if (viewWidthDefined) {
maxWidth = w;
maxHeight = Math.round(w * aspectRatio);
if (maxHeight <= 0) {
maxHeight = Math.round(w * aspectRatio);
}
}
if (viewHeightDefined) {
maxHeight = h;
maxWidth = Math.round(h / aspectRatio);
if (maxWidth <= 0) {
maxWidth = Math.round(h / aspectRatio);
}
}
}
}

// TODO padding and margins

measureChild(imageView, widthMeasureSpec, heightMeasureSpec);
if (isUpright) {
measureChild(imageView, widthMeasureSpec, heightMeasureSpec);
} else {
measureChild(imageView, heightMeasureSpec, widthMeasureSpec);
}

maxWidth = Math.max(maxWidth, imageView.getMeasuredWidth());
maxHeight = Math.max(maxHeight, imageView.getMeasuredHeight());
maxWidth = Math.max(maxWidth, isUpright ? imageView.getMeasuredWidth() : imageView.getMeasuredHeight());
maxHeight = Math.max(maxHeight, isUpright ? imageView.getMeasuredHeight() : imageView.getMeasuredWidth());

// Allow for zoom controls.
if (enableZoomControls) {
Expand Down Expand Up @@ -520,21 +572,12 @@ public void setColorFilter(ColorFilter filter)

private void updateScaleType()
{
if (orientation > 0 || enableZoomControls) {
imageView.setScaleType(ScaleType.MATRIX);
imageView.setAdjustViewBounds(false);
if (this.enableZoomControls || (this.viewWidthDefined && this.viewHeightDefined)) {
this.imageView.setAdjustViewBounds(false);
} else {
if (viewWidthDefined && viewHeightDefined) {
imageView.setAdjustViewBounds(false);
imageView.setScaleType(ScaleType.FIT_XY);
} else if (!enableScale) {
imageView.setAdjustViewBounds(false);
imageView.setScaleType(ScaleType.CENTER);
} else {
imageView.setAdjustViewBounds(true);
imageView.setScaleType(ScaleType.FIT_CENTER);
}
this.imageView.setAdjustViewBounds(true);
}
this.imageView.setScaleType(ScaleType.MATRIX);
requestLayout();
}

Expand All @@ -550,9 +593,12 @@ public void setHeightDefined(boolean defined)
updateScaleType();
}

public void setOrientation(int orientation)
public void setOrientation(TiExifOrientation exifOrientation)
{
this.orientation = orientation;
if (exifOrientation == null) {
exifOrientation = TiExifOrientation.UPRIGHT;
}
this.exifOrientation = exifOrientation;
updateScaleType();
}

Expand Down
Loading

0 comments on commit cce763a

Please sign in to comment.