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

timob-10080: Android: Parity for Ti.Blob methods #2833

Merged
merged 8 commits into from
Aug 30, 2012
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,25 @@ public void propertyChanged(String key, Object oldValue, Object newValue, KrollP
} else {
super.propertyChanged(key, oldValue, newValue, proxy);
}

View parentView = getParentView();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I understand the point of this is to avoid having the line in two places, I think we should avoid hitting the getParentView() call every single time that propertyChange is called. Please move this inside the if and else if blocks so we only hit it when we need it.

if (key.equals(TiC.PROPERTY_WIDTH)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what we talked about, it looks like you want to do this in a separate 'if' block because you are relying on logic from the super class that deals with height/width. If that is the case, can we move this whole block into the 'else' statement above to make this more clear.

Also a comment on why you are doing this would be nice.

if (TiC.LAYOUT_FILL.equals(TiConvert.toString(newValue)) && parentView != null) {
// Use the parent's width when it's fill
requestedWidth = TiConvert.toTiDimension(parentView.getMeasuredWidth(), TiDimension.TYPE_WIDTH);
} else {
requestedWidth = TiConvert.toTiDimension(newValue, TiDimension.TYPE_WIDTH);
}
setImage(true);
} else if (key.equals(TiC.PROPERTY_HEIGHT)) {
// Use the parent's height when it's fill
if (TiC.LAYOUT_FILL.equals(TiConvert.toString(newValue)) && parentView != null) {
requestedHeight = TiConvert.toTiDimension(parentView.getMeasuredHeight(), TiDimension.TYPE_HEIGHT);
} else {
requestedHeight = TiConvert.toTiDimension(newValue, TiDimension.TYPE_HEIGHT);
}
setImage(true);
}
}

public void onDestroy(Activity activity)
Expand Down
177 changes: 170 additions & 7 deletions android/titanium/src/java/org/appcelerator/titanium/TiBlob.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,28 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.HashMap;

import org.apache.commons.codec.binary.Base64;
import org.appcelerator.kroll.KrollDict;
import org.appcelerator.kroll.KrollProxy;
import org.appcelerator.kroll.annotations.Kroll;
import org.appcelerator.kroll.common.Log;
import org.appcelerator.kroll.util.KrollStreamHelper;
import org.appcelerator.titanium.io.TiBaseFile;
import org.appcelerator.titanium.io.TitaniumBlob;
import org.appcelerator.titanium.util.TiImageHelper;
import org.appcelerator.titanium.util.TiMimeTypeHelper;

import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.RectF;
import android.media.ThumbnailUtils;

/**
* A Titanium Blob object. A Blob can represent any opaque data or input stream.
Expand Down Expand Up @@ -60,16 +70,15 @@ public class TiBlob extends KrollProxy
private int type;
private Object data;
private String mimetype;
private int width, height;
private Bitmap image;

private TiBlob(int type, Object data, String mimetype)
{
super();
this.type = type;
this.data = data;
this.mimetype = mimetype;
this.width = 0;
this.height = 0;
this.image = null;
}

/**
Expand Down Expand Up @@ -125,8 +134,7 @@ public static TiBlob blobFromImage(Bitmap image)
}

TiBlob blob = new TiBlob(TYPE_IMAGE, data, "image/bitmap");
blob.width = image.getWidth();
blob.height = image.getHeight();
blob.image = image;
return blob;
}

Expand Down Expand Up @@ -332,13 +340,47 @@ public int getType()
@Kroll.getProperty @Kroll.method
public int getWidth()
{
return width;
if (image != null) {
return image.getWidth();
}

// Query the dimensions of a bitmap without allocating the memory for its pixels
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
switch (type) {
case TYPE_FILE:
BitmapFactory.decodeStream(getInputStream(), null, opts);
return opts.outWidth;
case TYPE_DATA:
byte[] byteArray = (byte[]) data;
BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length, opts);
return opts.outWidth;
}

return 0;
}

@Kroll.getProperty @Kroll.method
public int getHeight()
{
return height;
if (image != null) {
return image.getHeight();
}

// Query the dimensions of a bitmap without allocating the memory for its pixels
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
switch (type) {
case TYPE_FILE:
BitmapFactory.decodeStream(getInputStream(), null, opts);
return opts.outHeight;
case TYPE_DATA:
byte[] byteArray = (byte[]) data;
BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length, opts);
return opts.outHeight;
}

return 0;
}

@Kroll.method
Expand Down Expand Up @@ -402,4 +444,125 @@ public String toBase64()
{
return new String(Base64.encodeBase64(getBytes()));
}

public Bitmap getImage()
{
switch(type) {
case TYPE_FILE:
return BitmapFactory.decodeStream(getInputStream());
case TYPE_DATA:
byte[] byteArray = (byte[])data;
return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
}
return image;
}

@Kroll.method
public TiBlob imageAsCropped(Object params)
{
Bitmap img = getImage();
if (img == null) {
return null;
}
if (!(params instanceof HashMap)) {
Log.e(TAG, "Argument for imageAsCropped must be a dictionary");
return null;
}

KrollDict options = new KrollDict((HashMap) params);
int width = img.getWidth();
int height = img.getHeight();
int widthCropped = options.optInt(TiC.PROPERTY_WIDTH, width);
int heightCropped = options.optInt(TiC.PROPERTY_HEIGHT, height);
int x = options.optInt(TiC.PROPERTY_X, (width - widthCropped) / 2);
int y = options.optInt(TiC.PROPERTY_Y, (height - heightCropped) / 2);
Bitmap imageCropped = Bitmap.createBitmap(img, x, y, widthCropped, heightCropped);
return blobFromImage(imageCropped);
}

@Kroll.method
public TiBlob imageAsResized(Number width, Number height)
{
Bitmap img = getImage();
if (img == null) {
return null;
}

int dstWidth = width.intValue();
int dstHeight = height.intValue();
Bitmap imageResized = Bitmap.createScaledBitmap(img, dstWidth, dstHeight, true);
return blobFromImage(imageResized);
}

@Kroll.method
public TiBlob imageAsThumbnail(Number size, @Kroll.argument(optional = true) Number borderSize,
@Kroll.argument(optional = true) Number cornerRadius)
{
Bitmap img = getImage();
if (img == null) {
return null;
}

int thumbnailSize = size.intValue();
Bitmap imageThumbnail = ThumbnailUtils.extractThumbnail(img, thumbnailSize, thumbnailSize);

float border = 1f;
if (borderSize != null) {
border = borderSize.floatValue();
}
float radius = 0f;
if (cornerRadius != null) {
radius = cornerRadius.floatValue();
}

if (border == 0 && radius == 0) {
return blobFromImage(imageThumbnail);
}

Bitmap imageThumbnailBorder = TiImageHelper.imageWithRoundedCorner(imageThumbnail, radius, border);
return blobFromImage(imageThumbnailBorder);
}

@Kroll.method
public TiBlob imageWithAlpha()
{
Bitmap img = getImage();
if (img == null) {
return null;
}

Bitmap imageWithAlpha = TiImageHelper.imageWithAlpha(img);
return blobFromImage(imageWithAlpha);
}

@Kroll.method
public TiBlob imageWithRoundedCorner(Number cornerRadius, @Kroll.argument(optional = true) Number borderSize)
{
Bitmap img = getImage();
if (img == null) {
return null;
}

float radius = cornerRadius.floatValue();
float border = 1f;
if (borderSize != null) {
border = borderSize.floatValue();
}

Bitmap imageRoundedCorner = TiImageHelper.imageWithRoundedCorner(img, radius, border);
return blobFromImage(imageRoundedCorner);
}

@Kroll.method
public TiBlob imageWithTransparentBorder(Number size)
{
Bitmap img = getImage();
if (img == null) {
return null;
}

int borderSize = size.intValue();
Bitmap imageWithBorder = TiImageHelper.imageWithTransparentBorder(img, borderSize);
return blobFromImage(imageWithBorder);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* Appcelerator Titanium Mobile
* Copyright (c) 2009-2012 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/

package org.appcelerator.titanium.util;

import java.util.Arrays;

import org.appcelerator.kroll.common.Log;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.RectF;

/**
* Utility class for image manipulations.
*/
public class TiImageHelper
{
private static final String TAG = "TiImageHelper";

/**
* Add an alpha channel to the given image if it does not already have one.
*
* @param image
* the image to add an alpha channel to.
* @return a copy of the given image with an alpha channel. If the image already have the alpha channel, return the
* image itself.
*/
public static Bitmap imageWithAlpha(Bitmap image)
{
if (image == null) {
return null;
}
if (image.hasAlpha()) {
return image;
}
return image.copy(Bitmap.Config.ARGB_8888, true);
}

/**
* Create a copy of the given image with rounded corners and a transparent border around its edges.
*
* @param image
* the image to add rounded corners to.
* @param cornerRadius
* the radius of the rounded corners.
* @param borderSize
* the size of the border to be added.
* @return a copy of the given image with rounded corners and a transparent border. If the cornerRadius <= 0 or
* borderSize < 0, return the image itself.
*/
public static Bitmap imageWithRoundedCorner(Bitmap image, float cornerRadius, float borderSize)
{
if (image == null) {
return null;
}
if (cornerRadius <= 0 || borderSize < 0) {
Log.w(TAG, "Unable to add rounded corners. Invalid corner radius or borderSize for imageWithRoundedCorner");
return image;
}

int width = image.getWidth();
int height = image.getHeight();
Bitmap imageRoundedCorner = Bitmap.createBitmap(width + (int) (borderSize * 2), height + (int) (borderSize * 2),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(imageRoundedCorner);

Path clipPath = new Path();
RectF imgRect = new RectF(borderSize, borderSize, width + borderSize, height + borderSize);

float radii[] = new float[8];
Arrays.fill(radii, cornerRadius);
clipPath.addRoundRect(imgRect, radii, Direction.CW);

// This still happens sometimes when hw accelerated so, catch and warn
try {
canvas.clipPath(clipPath);
} catch (Exception e) {
Log.e(TAG, "Unable to create the image with rounded corners. clipPath failed on canvas: " + e.getMessage());
canvas.clipRect(imgRect);
}

canvas.drawBitmap(imageWithAlpha(image), borderSize, borderSize, null);
return imageRoundedCorner;
}

/**
* Add a transparent border to the given image around its edges.
*
* @param image
* the image to add a transparent border to.
* @param borderSize
* the size of the border to be added.
* @return a copy of the given image with a transparent border. If the borderSize <= 0, return the image itself.
*/
public static Bitmap imageWithTransparentBorder(Bitmap image, int borderSize)
{
if (image == null) {
return null;
}
if (borderSize <= 0) {
Log.w(TAG, "Unable to add a transparent border. Invalid border size for imageWithTransparentBorder.");
return image;
}

int width = image.getWidth();
int height = image.getHeight();
Bitmap imageBorder = Bitmap.createBitmap(width + borderSize * 2, height + borderSize * 2, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(imageBorder);
canvas.drawBitmap(imageWithAlpha(image), borderSize, borderSize, null);
return imageBorder;
}
}