Warp Dr. Bill!
Point to a place on the image and then hold down the mouse button to drag it to a new place. Then release the button.
public void run() { Initialize(); // need to keep a thread going to update status while warping // (this won't work from the ImageWarper thread for some reason) while( mStartup != null ) { try { Thread.sleep(500); } catch ( InterruptedException e ) {} if ( !mReady ) { // if warping, add a period on the end of the status message mStatus += "."; showStatus( mStatus ); } } } void Initialize() { mStatus = "Loading image..."; showStatus( mStatus ); mReady = false; mCanUndo = false; mRedo = false; mUndoButton.disable(); // get warp image & dimensions mImage = getImage( getCodeBase(), mImageName ); while ( (mWidth=mImage.getWidth(this)) this is not the way we're really try { Thread.sleep(100); } catch( InterruptedException e ) {} // supposed to do this. oh well, it works. while ( (mHeight="mImage.getHeight(this))" < 0 ) try { Thread.sleep(100); } catch( InterruptedException e ) {} // get the pixel data from the image mPixels="new" int[mWidth*mHeight]; mOldPixels="new" int[mWidth*mHeight]; PixelGrabber grabber="new" PixelGrabber( mImage, 0, 0, mWidth, mHeight, mPixels, 0, mWidth ); boolean done="false;" do { // grab pixels for 500 msec try { done="grabber.grabPixels(" 500 ); } catch ( InterruptedException e ) { mStatus="AlexWarp interrupted." ; showStatus( mStatus ); return; } // update status message mStatus +="." ; showStatus( mStatus ); } while( !done ); if ( (grabber.status() & ImageObserver.ABORT) !="0)" { mStatus="AlexWarp interrupted." ; showStatus( mStatus ); return; } mReady="true;" mStatus="Ready for warping. Click and drag in the image to warp it." ; showStatus( mStatus ); repaint(); } // called by ImageWarper thread when warping is complete void DoneWithWarping() { // the new picture is now in mPixels create a new Image from it mImage="createImage(" new MemoryImageSource(mWidth, mHeight, mPixels, 0, mWidth) ); // set button to "undo" state mUndoButton.enable(); mUndoButton.setLabel( "Undo" ); mRedo="false;" mCanUndo="true;" repaint(); // all done, ready for the next one! mReady="true;" mWarper="null;" mStatus="Ready for warping. Click and drag in the image to warp it." ; showStatus( mStatus ); } public void paint(Graphics g) { // draw image if ( mImage !="null" ) g.drawImage( mImage, kHOffset, kVOffset, this ); // if user is dragging, draw line if ( mFromPoint !="null" && mToPoint !="null" ) { g.setColor( Color.red ); g.drawLine( mFromPoint.x, mFromPoint.y, mToPoint.x, mToPoint.y ); g.setColor( Color.black ); } } public void update(Graphics g) { paint(g); } public void start() { if( mImage="=" null ) // have we initialized yet? { // add buttons Button b="new" Button( "Undo" ); add( b ); b.disable(); mUndoButton="b;" b="new" Button( "Stop" ); add( b ); b="new" Button( "Reset" ); add( b ); // get name of the image we're supposed to warp mImageName="getParameter(" "image" ); if ( mImageName="=" null ) mImageName="warp.gif" ; // default name // start initialization thread mStartup="new" Thread(this); mStartup.start(); } } public void stop() { if ( mStartup !="null" ) mStartup.stop(); mStartup="null;" if ( mWarper !="null" ) mWarper.stop(); mWarper="null;" } public boolean action( Event e, Object obj ) { if ( "Undo".equals( obj ) || "Redo".equals( obj ) ) { // user click on undo button int temp[]="mPixels;" mPixels="mOldPixels;" mOldPixels="temp;" mImage="createImage(" new MemoryImageSource(mWidth, mHeight, mPixels, 0, mWidth) ); mRedo="!mRedo;" mUndoButton.setLabel( mRedo ? "Redo" : "Undo" ); repaint(); return true; } else if ( "Stop".equals( obj ) ) { // user click on stop button if ( mWarper !="null" ) { mWarper.stop(); mWarper="null;" mReady="true;" mCanUndo="false;" mUndoButton.disable(); repaint(); mStatus="Ready for warping. Click and drag in the image to warp it." ; showStatus( mStatus ); } return true; } else if ( "Reset".equals( obj ) ) { // user click on reset button if ( mWarper !="null" ) { mWarper.stop(); mWarper="null;" } if ( mStartup !="null" ) { mStartup.stop(); mStartup="new" Thread(this); mStartup.start(); } return true; } return false; } // user begins to click-n-drag public boolean mouseDown( Event e, int x, int y ) { if ( !PointInImage( x, y ) || !mReady ) return false; mFromPoint="new" Point( x, y ); return true; } // user continues dragging public boolean mouseDrag( Event e, int x, int y ) { if ( mFromPoint="=" null ) return false; // set mToPoint, so that paint will draw it if ( mToPoint="=" null || !mToPoint.equals(mFromPoint) ) { mToPoint="ClipToImage(" mFromPoint, x, y ); repaint(); } return true; } // user is done dragging start warping! public boolean mouseUp( Event e, int x, int y ) { if ( mFromPoint="=" null ) return false; mReady="false;" mStatus="Warping..." ; showStatus( mStatus ); // swap new & old pixels; old pixels are saved in case of undo, new pixels will receive new image int temp[]="mOldPixels;" mOldPixels="mPixels;" mPixels="temp;" // start warp thread Point clipPoint="ClipToImage(" mFromPoint, x, y ); mWarper="new" ImageWarper( this, mOldPixels, mPixels, mWidth, mHeight, new Point( clipPoint.x kHOffset, clipPoint.y kVOffset ), new Point( mFromPoint.x kHOffset, mFromPoint.y kVOffset ) ); mWarper.start(); mFromPoint="mToPoint" return true; } // is this point inside the image area? boolean PointInImage( int x, int y ) { return ( x>= kHOffset && x <>= kVOffset && y clip line to the image area. returns a point that contains replacements for x & y Point ClipToImage( Point from, int x, int y ) { int dx="x" from.x, dy="y" from.y; if ( dx="=" 0 ) { if ( y < kVOffset ) y="kVOffset;" if ( y>= kVOffset + mHeight ) y = kVOffset + mHeight - 1; } else if ( dy == 0 ) { if ( x = kHOffset + mWidth ) x = kHOffset + mWidth - 1; } else { double slope = (double)dy / dx; if ( x <+ (int)(slope * (x-from.x)); } if ( x>= kHOffset + mWidth ) { x = kHOffset + mWidth - 1; y = from.y + (int)(slope * (x-from.x)); } if ( y <+ (int)((y-from.y) / slope); } if ( y>= kVOffset + mHeight ) { y = kVOffset + mHeight - 1; x = from.x + (int)((y-from.y) / slope); } } return new Point( x, y ); } } /* * ImageWarper is the class that does the actual warping. Give it two * pixels buffers - one with the original image, and one to hold the new * image. It calls DoneWithWarping to let you know when it's all done. */ class ImageWarper extends Thread { AlexWarp mAlexWarp; Point mFromPoint, mToPoint; int mFromPixels[], mToPixels[]; int mWidth, mHeight; // width & height of warp image ImageWarper( AlexWarp j, int fromPixels[], int toPixels[], int w, int h, Point fromPoint, Point toPoint ) { mAlexWarp = j; mFromPixels = fromPixels; mToPixels = toPixels; mFromPoint = fromPoint; mToPoint = toPoint; mWidth = w; mHeight = h; } // warp the pixels, then notify the applet public void run() { WarpPixels(); mAlexWarp.DoneWithWarping(); } // warp mFromPixels into mToPixels void WarpPixels() { int dx = mToPoint.x-mFromPoint.x, dy = mToPoint.y-mFromPoint.y, dist = (int)Math.sqrt(dx*dx+dy*dy)*2; Rectangle r = new Rectangle(); Point ne = new Point(0,0), nw = new Point(0,0), se = new Point(0,0), sw = new Point(0,0); // copy mFromPixels to mToPixels, so the non-warped parts will be identical System.arraycopy( mFromPixels, 0, mToPixels, 0, mWidth*mHeight ); if ( dist == 0 ) return; // warp northeast quadrant SetRect( r, mFromPoint.x - dist, mFromPoint.y - dist, mFromPoint.x, mFromPoint.y ); ClipRect( r, mWidth, mHeight ); SetPt( ne, r.x, r.y ); SetPt( nw, r.x+r.width, r.y ); SetPt( se, r.x, r.y+r.height ); SetPt( sw, mToPoint.x, mToPoint.y ); WarpRegion( r, nw, ne, sw, se ); // warp nortwest quadrant SetRect( r, mFromPoint.x, mFromPoint.y - dist, mFromPoint.x + dist, mFromPoint.y ); ClipRect( r, mWidth, mHeight ); SetPt( ne, r.x, r.y ); SetPt( nw, r.x+r.width, r.y ); SetPt( se, mToPoint.x, mToPoint.y ); SetPt( sw, r.x+r.width, r.y+r.height ); WarpRegion( r, nw, ne, sw, se ); // warp southeast quadrant SetRect( r, mFromPoint.x - dist, mFromPoint.y, mFromPoint.x, mFromPoint.y + dist ); ClipRect( r, mWidth, mHeight ); SetPt( ne, r.x, r.y ); SetPt( nw, mToPoint.x, mToPoint.y ); SetPt( se, r.x, r.y+r.height ); SetPt( sw, r.x+r.width, r.y+r.height ); WarpRegion( r, nw, ne, sw, se ); // warp southwest quadrant SetRect( r, mFromPoint.x, mFromPoint.y, mFromPoint.x + dist, mFromPoint.y + dist ); ClipRect( r, mWidth, mHeight ); SetPt( ne, mToPoint.x, mToPoint.y ); SetPt( nw, r.x+r.width, r.y ); SetPt( se, r.x, r.y+r.height ); SetPt( sw, r.x+r.width, r.y+r.height ); WarpRegion( r, nw, ne, sw, se ); } // warp a quadrilateral into a rectangle (double-secret magic code!) void WarpRegion( Rectangle fromRect, Point nw, Point ne, Point sw, Point se ) { int dx = fromRect.width, dy = fromRect.height; double invDX = 1.0/dx, invDY = 1.0/dy; for ( int a = 0; a = mWidth ) xin = mWidth - 1; if ( yin = mHeight ) yin = mHeight - 1; int pixelValue = mFromPixels[ (int)xin + (int)yin * mWidth ]; mToPixels[ toPixel ] = pixelValue; xin += dxin; yin += dyin; toPixel += mWidth; } } } void ClipRect( Rectangle r, int w, int h ) { if ( r.x = w ) r.width = w - r.x - 1; if ( r.y+r.height >= h ) r.height = h - r.y - 1; } // SetRect and SetPt are Mac OS functions. I wrote my own versions here // so I didn't have to rewrite too much of the code. void SetRect( Rectangle r, int left, int top, int right, int bottom ) { r.x = left; r.y = top; r.width = right-left; r.height = bottom-top; } void SetPt( Point pt, int x, int y ) { pt.x = x; pt.y = y; } }