package com.aviary.android.feather.effects;
import android.R.attr;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.os.AsyncTask;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageButton;
import android.widget.ImageView;
import com.aviary.android.feather.R;
import com.aviary.android.feather.graphics.CropCheckboxDrawable;
import com.aviary.android.feather.graphics.DefaultGalleryCheckboxDrawable;
import com.aviary.android.feather.graphics.GalleryCircleDrawable;
import com.aviary.android.feather.graphics.PreviewCircleDrawable;
import com.aviary.android.feather.library.filters.FilterLoaderFactory;
import com.aviary.android.feather.library.filters.FilterLoaderFactory.Filters;
import com.aviary.android.feather.library.filters.IFilter;
import com.aviary.android.feather.library.filters.SpotBrushFilter;
import com.aviary.android.feather.library.graphics.FlattenPath;
import com.aviary.android.feather.library.moa.Moa;
import com.aviary.android.feather.library.moa.MoaAction;
import com.aviary.android.feather.library.moa.MoaActionFactory;
import com.aviary.android.feather.library.moa.MoaActionList;
import com.aviary.android.feather.library.services.ConfigService;
import com.aviary.android.feather.library.services.EffectContext;
import com.aviary.android.feather.library.utils.BitmapUtils;
import com.aviary.android.feather.library.utils.SystemUtils;
import com.aviary.android.feather.library.utils.UIConfiguration;
import com.aviary.android.feather.utils.UIUtils;
import com.aviary.android.feather.widget.AdapterView;
import com.aviary.android.feather.widget.Gallery;
import com.aviary.android.feather.widget.Gallery.OnItemsScrollListener;
import com.aviary.android.feather.widget.IToast;
import com.aviary.android.feather.widget.ImageViewSpotDraw;
import com.aviary.android.feather.widget.ImageViewSpotDraw.OnDrawListener;
import com.aviary.android.feather.widget.ImageViewSpotDraw.TouchMode;
/**
* The Class SpotDrawPanel.
*/
public class SpotDrawPanel extends AbstractContentPanel implements OnDrawListener {
/** The brush size. */
protected int mBrushSize;
/** The filter type. */
protected Filters mFilterType;
protected Gallery mGallery;
/** The brush sizes. */
protected int[] mBrushSizes;
/** The current selected view. */
protected View mSelected;
/** The current selected position. */
protected int mSelectedPosition = 0;
protected ImageButton mLensButton;
/** The background draw thread. */
private MyHandlerThread mBackgroundDrawThread;
IToast mToast;
PreviewCircleDrawable mCircleDrawablePreview;
MoaActionList mActions = MoaActionFactory.actionList();
private int mPreviewWidth, mPreviewHeight;
/**
* Show size preview.
*
* @param size
* the size
*/
private void showSizePreview( int size ) {
if ( !isActive() ) return;
mToast.show();
updateSizePreview( size );
}
/**
* Hide size preview.
*/
private void hideSizePreview() {
if ( !isActive() ) return;
mToast.hide();
}
/**
* Update size preview.
*
* @param size
* the size
*/
private void updateSizePreview( int size ) {
if ( !isActive() ) return;
mCircleDrawablePreview.setRadius( size );
View v = mToast.getView();
v.findViewById( R.id.size_preview_image );
v.invalidate();
}
/**
* Instantiates a new spot draw panel.
*
* @param context
* the context
* @param filter_type
* the filter_type
*/
public SpotDrawPanel( EffectContext context, Filters filter_type ) {
super( context );
mFilterType = filter_type;
}
/*
* (non-Javadoc)
*
* @see com.aviary.android.feather.effects.AbstractEffectPanel#onCreate(android.graphics.Bitmap)
*/
@Override
public void onCreate( Bitmap bitmap ) {
super.onCreate( bitmap );
mFilter = createFilter();
ConfigService config = getContext().getService( ConfigService.class );
mBrushSizes = config.getSizeArray( R.array.feather_spot_brush_sizes );
mBrushSize = mBrushSizes[0];
mLensButton = (ImageButton) getContentView().findViewById( R.id.lens_button );
mImageView = (ImageViewSpotDraw) getContentView().findViewById( R.id.image );
( (ImageViewSpotDraw) mImageView ).setBrushSize( (float) mBrushSize );
mPreview = BitmapUtils.copy( mBitmap, Config.ARGB_8888 );
mPreviewWidth = mPreview.getWidth();
mPreviewHeight = mPreview.getHeight();
mImageView.setImageBitmap( mPreview, true, getContext().getCurrentImageViewMatrix(), UIConfiguration.IMAGE_VIEW_MAX_ZOOM );
int defaultOption = config.getInteger( R.integer.feather_spot_brush_selected_size_index );
defaultOption = Math.min( Math.max( defaultOption, 0 ), mBrushSizes.length - 1 );
mGallery = (Gallery) getOptionView().findViewById( R.id.gallery );
mGallery.setCallbackDuringFling( false );
mGallery.setSpacing( 0 );
mGallery.setOnItemsScrollListener( new OnItemsScrollListener() {
@Override
public void onScrollFinished( AdapterView<?> parent, View view, int position, long id ) {
mLogger.info( "onScrollFinished: " + position );
mBrushSize = mBrushSizes[position];
( (ImageViewSpotDraw) mImageView ).setBrushSize( (float) mBrushSize );
setSelectedTool( TouchMode.DRAW );
updateSelection( view, position );
hideSizePreview();
}
@Override
public void onScrollStarted( AdapterView<?> parent, View view, int position, long id ) {
showSizePreview( mBrushSizes[position] );
setSelectedTool( TouchMode.DRAW );
}
@Override
public void onScroll( AdapterView<?> parent, View view, int position, long id ) {
updateSizePreview( mBrushSizes[position] );
}
} );
mBackgroundDrawThread = new MyHandlerThread( "filter-thread", Thread.MIN_PRIORITY );
initAdapter();
}
/**
* Inits the adapter.
*/
private void initAdapter() {
int height = mGallery.getHeight();
if ( height < 1 ) {
mGallery.getHandler().post( new Runnable() {
@Override
public void run() {
initAdapter();
}
} );
return;
}
mGallery.setSelection( 2, false, true );
mGallery.setAdapter( new GalleryAdapter( getContext().getBaseContext(), mBrushSizes ) );
}
/*
* (non-Javadoc)
*
* @see com.aviary.android.feather.effects.AbstractEffectPanel#onActivate()
*/
@Override
public void onActivate() {
super.onActivate();
( (ImageViewSpotDraw) mImageView ).setOnDrawStartListener( this );
mBackgroundDrawThread.start();
mBackgroundDrawThread.setRadius( (float) Math.max( 1, mBrushSizes[0] ), mPreviewWidth );
updateSelection( (View) mGallery.getSelectedView(), mGallery.getSelectedItemPosition() );
mToast = IToast.make( getContext().getBaseContext(), -1 );
mCircleDrawablePreview = new PreviewCircleDrawable( 0 );
ImageView image = (ImageView) mToast.getView().findViewById( R.id.size_preview_image );
image.setImageDrawable( mCircleDrawablePreview );
mLensButton.setOnClickListener( new OnClickListener() {
@Override
public void onClick( View arg0 ) {
// boolean selected = arg0.isSelected();
setSelectedTool( ( (ImageViewSpotDraw) mImageView ).getDrawMode() == TouchMode.DRAW ? TouchMode.IMAGE : TouchMode.DRAW );
}
} );
mLensButton.setVisibility( View.VISIBLE );
contentReady();
}
/*
* (non-Javadoc)
*
* @see com.aviary.android.feather.effects.AbstractContentPanel#onDispose()
*/
@Override
protected void onDispose() {
mContentReadyListener = null;
super.onDispose();
}
/**
* Update selection.
*
* @param newSelection
* the new selection
* @param position
* the position
*/
protected void updateSelection( View newSelection, int position ) {
if ( mSelected != null ) {
mSelected.setSelected( false );
}
mSelected = newSelection;
mSelectedPosition = position;
if ( mSelected != null ) {
mSelected = newSelection;
mSelected.setSelected( true );
}
}
/**
* Sets the selected tool.
*
* @param which
* the new selected tool
*/
private void setSelectedTool( TouchMode which ) {
( (ImageViewSpotDraw) mImageView ).setDrawMode( which );
mLensButton.setSelected( which == TouchMode.IMAGE );
setPanelEnabled( which != TouchMode.IMAGE );
}
/*
* (non-Javadoc)
*
* @see com.aviary.android.feather.effects.AbstractEffectPanel#onDeactivate()
*/
@Override
public void onDeactivate() {
( (ImageViewSpotDraw) mImageView ).setOnDrawStartListener( null );
if ( mBackgroundDrawThread != null ) {
if ( mBackgroundDrawThread.isAlive() ) {
mBackgroundDrawThread.quit();
while ( mBackgroundDrawThread.isAlive() ) {
// wait...
}
}
}
onProgressEnd();
super.onDeactivate();
}
/*
* (non-Javadoc)
*
* @see com.aviary.android.feather.effects.AbstractEffectPanel#onDestroy()
*/
@Override
public void onDestroy() {
super.onDestroy();
mBackgroundDrawThread = null;
mImageView.clear();
mToast.hide();
}
/*
* (non-Javadoc)
*
* @see com.aviary.android.feather.effects.AbstractEffectPanel#onCancelled()
*/
@Override
public void onCancelled() {
super.onCancelled();
}
/*
* (non-Javadoc)
*
* @see com.aviary.android.feather.widget.ImageViewSpotDraw.OnDrawListener#onDrawStart(float[], int)
*/
@Override
public void onDrawStart( float[] points, int radius ) {
radius = Math.max( 1, radius );
mLogger.info( "onDrawStart. radius: " + radius );
mBackgroundDrawThread.setRadius( (float) radius, mPreviewWidth );
mBackgroundDrawThread.moveTo( points );
mBackgroundDrawThread.lineTo( points );
setIsChanged( true );
}
/*
* (non-Javadoc)
*
* @see com.aviary.android.feather.widget.ImageViewSpotDraw.OnDrawListener#onDrawing(float[], int)
*/
@Override
public void onDrawing( float[] points, int radius ) {
mBackgroundDrawThread.quadTo( points );
}
/*
* (non-Javadoc)
*
* @see com.aviary.android.feather.widget.ImageViewSpotDraw.OnDrawListener#onDrawEnd()
*/
@Override
public void onDrawEnd() {
// TODO: empty
mLogger.info( "onDrawEnd" );
}
/*
* (non-Javadoc)
*
* @see com.aviary.android.feather.effects.AbstractEffectPanel#onGenerateResult()
*/
@Override
protected void onGenerateResult() {
mLogger.info( "onGenerateResult: " + mBackgroundDrawThread.isCompleted() + ", " + mBackgroundDrawThread.isAlive() );
if ( !mBackgroundDrawThread.isCompleted() && mBackgroundDrawThread.isAlive() ) {
GenerateResultTask task = new GenerateResultTask();
task.execute();
} else {
onComplete( mPreview, mActions );
}
}
/**
* Sets the panel enabled.
*
* @param value
* the new panel enabled
*/
public void setPanelEnabled( boolean value ) {
if ( mOptionView != null ) {
if ( value != mOptionView.isEnabled() ) {
mOptionView.setEnabled( value );
if ( value ) {
getContext().restoreToolbarTitle();
} else {
getContext().setToolbarTitle( R.string.zoom_mode );
}
mOptionView.findViewById( R.id.disable_status ).setVisibility( value ? View.INVISIBLE : View.VISIBLE );
}
}
}
/**
* Prints the rect.
*
* @param rect
* the rect
* @return the string
*/
@SuppressWarnings("unused")
private String printRect( Rect rect ) {
return "( left=" + rect.left + ", top=" + rect.top + ", width=" + rect.width() + ", height=" + rect.height() + ")";
}
/**
* Creates the filter.
*
* @return the i filter
*/
protected IFilter createFilter() {
return FilterLoaderFactory.get( mFilterType );
}
/*
* (non-Javadoc)
*
* @see com.aviary.android.feather.effects.AbstractContentPanel#generateContentView(android.view.LayoutInflater)
*/
@Override
protected View generateContentView( LayoutInflater inflater ) {
return inflater.inflate( R.layout.feather_spotdraw_content, null );
}
/*
* (non-Javadoc)
*
* @see com.aviary.android.feather.effects.AbstractOptionPanel#generateOptionView(android.view.LayoutInflater,
* android.view.ViewGroup)
*/
@Override
protected ViewGroup generateOptionView( LayoutInflater inflater, ViewGroup parent ) {
return (ViewGroup) inflater.inflate( R.layout.feather_pixelbrush_panel, parent, false );
}
/*
* (non-Javadoc)
*
* @see com.aviary.android.feather.effects.AbstractContentPanel#getContentDisplayMatrix()
*/
@Override
public Matrix getContentDisplayMatrix() {
return mImageView.getDisplayMatrix();
}
/**
* background draw thread
*/
class MyHandlerThread extends Thread {
/** The started. */
boolean started;
/** The running. */
volatile boolean running;
/** The paused. */
boolean paused;
/** The m x. */
float mX = 0;
/** The m y. */
float mY = 0;
/** The m flatten path. */
FlattenPath mFlattenPath;
/**
* Instantiates a new my handler thread.
*
* @param name
* the name
* @param priority
* the priority
*/
public MyHandlerThread( String name, int priority ) {
super( name );
setPriority( priority );
init();
}
/**
* Inits the.
*/
void init() {
mFlattenPath = new FlattenPath( 0.1 );
}
/*
* (non-Javadoc)
*
* @see java.lang.Thread#start()
*/
@Override
synchronized public void start() {
started = true;
running = true;
super.start();
}
/**
* Quit.
*/
synchronized public void quit() {
running = false;
pause();
interrupt();
};
/**
* Pause.
*/
public void pause() {
if ( !started ) throw new IllegalAccessError( "thread not started" );
paused = true;
boolean stopped = ( (SpotBrushFilter) mFilter ).stop();
mLogger.log( "pause. filter stopped: " + stopped );
}
/**
* Unpause.
*/
public void unpause() {
if ( !started ) throw new IllegalAccessError( "thread not started" );
paused = false;
}
/** The m radius. */
float mRadius = 10;
/**
* Sets the radius.
*
* @param radius
* the new radius
*/
public void setRadius( float radius, int bitmapWidth ) {
( (SpotBrushFilter) mFilter ).setRadius( radius, bitmapWidth );
mRadius = radius;
}
/**
* Move to.
*
* @param values
* the values
*/
public void moveTo( float values[] ) {
mFlattenPath.moveTo( values[0], values[1] );
mX = values[0];
mY = values[1];
}
/**
* Line to.
*
* @param values
* the values
*/
public void lineTo( float values[] ) {
mFlattenPath.lineTo( values[0], values[1] );
mX = values[0];
mY = values[1];
}
/**
* Quad to.
*
* @param values
* the values
*/
public void quadTo( float values[] ) {
mFlattenPath.quadTo( mX, mY, ( values[0] + mX ) / 2, ( values[1] + mY ) / 2 );
mX = values[0];
mY = values[1];
}
/**
* Checks if is completed.
*
* @return true, if is completed
*/
public boolean isCompleted() {
return mFlattenPath.size() == 0;
}
/**
* Queue size.
*
* @return the int
*/
public int queueSize() {
return mFlattenPath.size();
}
/**
* Gets the lerp.
*
* @param pt1
* the pt1
* @param pt2
* the pt2
* @param t
* the t
* @return the lerp
*/
public PointF getLerp( PointF pt1, PointF pt2, float t ) {
return new PointF( pt1.x + ( pt2.x - pt1.x ) * t, pt1.y + ( pt2.y - pt1.y ) * t );
}
/** The m last point. */
PointF mLastPoint;
/*
* (non-Javadoc)
*
* @see java.lang.Thread#run()
*/
@Override
public void run() {
while ( !started ) {}
boolean s = false;
mLogger.log( "thread.start!" );
while ( running ) {
if ( paused ) {
continue;
}
int currentSize;
currentSize = mFlattenPath.size();
if ( currentSize > 0 && !isInterrupted() ) {
if ( !s ) {
mLogger.log( "start: " + currentSize );
s = true;
onProgressStart();
}
PointF firstPoint;
firstPoint = mFlattenPath.remove();
if ( mLastPoint == null ) {
mLastPoint = firstPoint;
continue;
}
if ( firstPoint == null ) {
mLastPoint = null;
continue;
}
float currentPosition = 0;
// float length = mLastPoint.length( firstPoint.x, firstPoint.y );
float x = Math.abs( firstPoint.x - mLastPoint.x );
float y = Math.abs( firstPoint.y - mLastPoint.y );
float length = (float) Math.sqrt( x * x + y * y );
float lerp;
if ( length == 0 ) {
( (SpotBrushFilter) mFilter ).draw( firstPoint.x / mPreviewWidth, firstPoint.y / mPreviewHeight, mPreview );
try {
mActions.add( (MoaAction) ( (SpotBrushFilter) mFilter ).getActions().get( 0 ).clone() );
} catch ( CloneNotSupportedException e ) {
e.printStackTrace();
}
} else {
while ( currentPosition < length ) {
lerp = currentPosition / length;
PointF point = getLerp( mLastPoint, firstPoint, lerp );
currentPosition += mRadius;
( (SpotBrushFilter) mFilter ).draw( point.x / mPreviewWidth, point.y / mPreviewHeight, mPreview );
try {
mActions.add( (MoaAction) ( (SpotBrushFilter) mFilter ).getActions().get( 0 ).clone() );
} catch ( CloneNotSupportedException e ) {
e.printStackTrace();
}
if ( SystemUtils.isHoneyComb() ) {
// There's a bug in Honeycomb which prevent the bitmap to be updated on a glcanvas
// so we need to force it
Moa.notifyPixelsChanged( mPreview );
}
}
}
mLastPoint = firstPoint;
mImageView.postInvalidate();
} else {
if ( s ) {
mLogger.log( "end: " + currentSize );
onProgressEnd();
s = false;
}
}
}
onProgressEnd();
mLogger.log( "thread.end" );
};
};
/**
* Bottom Gallery adapter.
*
* @author alessandro
*/
class GalleryAdapter extends BaseAdapter {
private int[] sizes;
LayoutInflater mLayoutInflater;
Drawable checkbox_unselected, checkbox_selected;
Resources mRes;
/**
* Instantiates a new gallery adapter.
*
* @param context
* the context
* @param values
* the values
*/
public GalleryAdapter( Context context, int[] values ) {
mLayoutInflater = UIUtils.getLayoutInflater();
sizes = values;
mRes = getContext().getBaseContext().getResources();
checkbox_selected = mRes.getDrawable( R.drawable.feather_crop_checkbox_selected );
checkbox_unselected = mRes.getDrawable( R.drawable.feather_crop_checkbox_unselected );
}
/*
* (non-Javadoc)
*
* @see android.widget.Adapter#getCount()
*/
@Override
public int getCount() {
return sizes.length;
}
/*
* (non-Javadoc)
*
* @see android.widget.Adapter#getItem(int)
*/
@Override
public Object getItem( int position ) {
return sizes[position];
}
/*
* (non-Javadoc)
*
* @see android.widget.Adapter#getItemId(int)
*/
@Override
public long getItemId( int position ) {
return 0;
}
/*
* (non-Javadoc)
*
* @see android.widget.Adapter#getView(int, android.view.View, android.view.ViewGroup)
*/
@Override
public View getView( int position, View convertView, ViewGroup parent ) {
final boolean valid = position >= 0 && position < getCount();
GalleryCircleDrawable mCircleDrawable = null;
int biggest = sizes[sizes.length - 1];
int size = 1;
View view;
if ( convertView == null ) {
if ( valid ) {
mCircleDrawable = new GalleryCircleDrawable( 1, 0 );
view = mLayoutInflater.inflate( R.layout.feather_checkbox_button, mGallery, false );
StateListDrawable st = new StateListDrawable();
Drawable d1 = new CropCheckboxDrawable( mRes, false, mCircleDrawable, 0.67088f, 0.4f, 0.0f );
Drawable d2 = new CropCheckboxDrawable( mRes, true, mCircleDrawable, 0.67088f, 0.4f, 0.0f );
st.addState( new int[] { -attr.state_selected }, d1 );
st.addState( new int[] { attr.state_selected }, d2 );
view.setBackgroundDrawable( st );
view.setTag( mCircleDrawable );
} else {
// use the blank view
view = mLayoutInflater.inflate( R.layout.feather_checkbox_button, mGallery, false );
Drawable unselectedBackground = new DefaultGalleryCheckboxDrawable( mRes, false );
view.setBackgroundDrawable( unselectedBackground );
}
} else {
view = convertView;
if ( valid ) {
mCircleDrawable = (GalleryCircleDrawable) view.getTag();
}
}
if ( mCircleDrawable != null && valid ) {
size = sizes[position];
float value = (float) size / biggest;
mCircleDrawable.update( value, 0 );
}
view.setSelected( mSelectedPosition == position );
return view;
}
}
/**
* GenerateResultTask is used when the background draw operation is still running. Just wait until the draw operation completed.
*/
class GenerateResultTask extends AsyncTask<Void, Void, Void> {
/** The m progress. */
ProgressDialog mProgress = new ProgressDialog( getContext().getBaseContext() );
/*
* (non-Javadoc)
*
* @see android.os.AsyncTask#onPreExecute()
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
mProgress.setTitle( getContext().getBaseContext().getString( R.string.feather_loading_title ) );
mProgress.setMessage( getContext().getBaseContext().getString( R.string.effect_loading_message ) );
mProgress.setIndeterminate( true );
mProgress.setCancelable( false );
mProgress.show();
}
/*
* (non-Javadoc)
*
* @see android.os.AsyncTask#doInBackground(Params[])
*/
@Override
protected Void doInBackground( Void... params ) {
if ( mBackgroundDrawThread != null ) {
mLogger.info( "GenerateResultTask::doInBackground", mBackgroundDrawThread.isCompleted() );
while ( mBackgroundDrawThread != null && !mBackgroundDrawThread.isCompleted() ) {
mLogger.log( "waiting.... " + mBackgroundDrawThread.queueSize() );
try {
Thread.sleep( 100 );
} catch ( InterruptedException e ) {
e.printStackTrace();
}
}
}
return null;
}
/*
* (non-Javadoc)
*
* @see android.os.AsyncTask#onPostExecute(java.lang.Object)
*/
@Override
protected void onPostExecute( Void result ) {
super.onPostExecute( result );
mLogger.info( "GenerateResultTask::onPostExecute" );
if ( getContext().getBaseActivity().isFinishing() ) return;
if ( mProgress.isShowing() ) {
try {
mProgress.dismiss();
} catch ( IllegalArgumentException e ) {}
}
onComplete( mPreview, mActions );
}
}
}