package com.informagen; import java.awt.Image; import java.awt.image.ImageProducer; import java.awt.image.MemoryImageSource; import java.awt.image.ColorModel; import java.awt.image.DirectColorModel; import java.awt.image.IndexColorModel; import java.awt.Toolkit; import com.apple.mrj.MRJOSType; import com.apple.mrj.jdirect.GenericHandle; import com.apple.mrj.jdirect.HandleStruct; import com.apple.mrj.jdirect.ByteArrayStruct; import com.apple.mrj.jdirect.PointerStruct; import com.apple.mrj.jdirect.Struct; // Package containing the LOCK object import com.apple.mrj.macos.toolbox.Toolbox; import com.apple.mrj.macos.libraries.InterfaceLib; public class MacClipboard { public static boolean isMacintosh() { return System.getProperty("os.name").equalsIgnoreCase("Mac OS"); } public static boolean isTEXT() { return isType("TEXT"); } public static boolean isPICT() { return isType("PICT"); } public static boolean isType(String inType){ MRJOSType resType = new MRJOSType(inType); return isType(resType); } public static boolean isType(MRJOSType resType){ int jDirectHdl = MemoryFunctions.NewHandle(0); int[] dontCare = new int[1]; int scrapReturn = ScrapFunctions.GetScrap(jDirectHdl, resType.toInt(), dontCare); MemoryFunctions.DisposeHandle(jDirectHdl); return scrapReturn != ErrorConstants.noTypeErr; } // The current MRJ virtual machine supports this type of scrap, ie // // clipboard = getToolkit().getSystemClipboard(); // Transferable contents = clipboard.getContents(this); // String string = (String) contents.getTransferData(DataFlavor.stringFlavor); // // However, this will work as well and is included for completeness public static String getText() { // Must be scrap type of 'TEXT' MRJOSType TEXTtype = new MRJOSType("TEXT"); short osErr; int jDirectHdl = MemoryFunctions.NewHandle(0); int[] dontCare = new int[1]; int scrapReturn = ScrapFunctions.GetScrap(jDirectHdl, TEXTtype.toInt(), dontCare); String returnString = null; if ( scrapReturn != ErrorConstants.noTypeErr ) { int size = MemoryFunctions.GetHandleSize(jDirectHdl); GenericHandle scrapHdl = new GenericHandle(jDirectHdl); byte[] bytes = scrapHdl.getBytesAt(0, size); returnString = new String(bytes); } MemoryFunctions.DisposeHandle(jDirectHdl); return returnString; } public static Image getImage() { // These variables set within the synchronized block but are // referenced outside of it int pixelSize = 0; int pixelType = 0; int cmpSize = 0; int colorTable = 0; short rowBytes = 0; byte[] pixels = null; short osErr; int jDirectHdl = MemoryFunctions.NewHandle(0); int[] dontCare = new int[1]; // Test for PICT scrap type int scrapReturn = ScrapFunctions.GetScrap(jDirectHdl, new MRJOSType("PICT").toInt(), dontCare); if ( scrapReturn == ErrorConstants.noTypeErr ) { MemoryFunctions.DisposeHandle(jDirectHdl); return null; } // OK, we have a 'PICT' from clipboard, turn it into a "java.awt.Image" ////////////////////// // Get and lock the scrap handle in order to obtain the PICT bounding rectangle GenericHandle hDest = new GenericHandle(jDirectHdl); MemoryFunctions.HLock(jDirectHdl); short height = (short)(hDest.getShortAt(6) - hDest.getShortAt(2)); short width = (short)(hDest.getShortAt(8) - hDest.getShortAt(4)); MemoryFunctions.HUnlock(jDirectHdl); // Don't let JVM AWT peers at the Mac Toolbox while we are working. synchronized( Toolbox.LOCK ) { // Save the current GWorld, which is the Monitor's Desktop // JDirect uses single element arrays used to retrieve Toolbox values int [] refPort = new int[1]; int [] refGDhdl = new int[1]; QDOffscreenFunctions.GetGWorld(refPort, refGDhdl); // Dereference the first value of each array int currPort = refPort[0]; GDeviceStruct gds = new GDeviceStruct(refGDhdl[0]); // Create an offscreen GWorld in which to render this PICT scrap // Use a single element array to get the reference to the offscreenGWorld int[] refInt = new int[1]; short PixelDepth = 0; RectStruct boundsRect = new RectStruct(); boundsRect.setTop((short)0); boundsRect.setLeft((short)0); boundsRect.setBottom(height); boundsRect.setRight(width); osErr = QDOffscreenFunctions.NewGWorld(refInt, PixelDepth, boundsRect, null, null, 0); int offscreenGWorld = refInt[0]; // Make the offscreen GWorld the one in which to do the drawing // Create a PictureStruct from the 'PICT' scrap handle, pass it // to DrawPicture to be rendered. QDOffscreenFunctions.SetGWorld(offscreenGWorld, gds); PictureStruct aPict = new PictureStruct(jDirectHdl); QuickdrawFunctions.DrawPicture(aPict, boundsRect); // Now that we have the 'PICT' drawn for us, we will convert it's pixels into // an array of Java ints in order to make an Image. PixMapStruct pm = new PixMapStruct(QDOffscreenFunctions.GetGWorldPixMap(offscreenGWorld)); int packType = pm.getPackType(); rowBytes = (short)(pm.getRowBytes() & 0x3fff); pixelType = pm.getPixelType(); pixelSize = pm.getPixelSize(); colorTable = pm.getPmTable(); cmpSize = pm.getCmpSize(); int baseAddr = QDOffscreenFunctions.GetPixBaseAddr(pm); QDOffscreenFunctions.LockPixels(pm); ImageStruct pix = new ImageStruct(baseAddr, rowBytes, height); pixels = pix.getBytes(); QDOffscreenFunctions.UnlockPixels(pm); // Restore the GWorld to the main screen. QDOffscreenFunctions.SetGWorld(currPort, gds); // Dispose of structures and handles //QuickdrawFunctions.DisposePixMap(pm); QDOffscreenFunctions.DisposeGWorld(offscreenGWorld); MemoryFunctions.DisposeHandle(jDirectHdl); } // Using the raw pixel data obtained from the Toolbox create an ImageProducer // and then an image. ImageProducer pixelSource = null; if ( pixelType == QuickdrawConstants.RGBDirect ) pixelSource = fromRGBDirect(pixelSize, cmpSize, height, width, rowBytes, pixels); else if ( pixelType == QuickdrawConstants.clutType ) pixelSource = fromCLUT(colorTable, cmpSize, height, width, rowBytes, pixels); // Create and return the java.awt.Image if (pixelSource != null) return Toolkit.getDefaultToolkit().createImage(pixelSource); else return null; } private static ImageProducer fromRGBDirect(int pixelSize, int cmpSize, short height, short width, int rowBytes, byte[] pixels) { int i = 0; int ii = 0; int row = 0; int bytesPerPixel = pixelSize/8; int[] imagePixels = new int[(rowBytes/bytesPerPixel) * height]; while ( row++ < height ) { for (int j=0; j<rowBytes; j=j+bytesPerPixel) { // Build the Quickdraw pixel from raw bytes int pixel = 0; for (int jj=0; jj<bytesPerPixel; jj++) { pixel <<= 8; pixel += ((int)pixels[i++]) & 0xFF; } // Extract out the r,g,b values int mask = 1; mask <<= cmpSize; mask--; int b = pixel & mask; pixel >>= cmpSize; int g = pixel & mask; pixel >>= cmpSize; int r = pixel & mask; r <<= 8 - cmpSize; g <<= 8 - cmpSize; b <<= 8 - cmpSize; // Repack the r,g,b values into an java.awt.Image int array imagePixels[ii++] = 0xFF <<24 | r << 16 | g << 8 | b; } } return new MemoryImageSource(width, height, imagePixels, 0, rowBytes/bytesPerPixel); } // // As of MRJ 2.1.2 "DirectColorModel" did not work properly for all color depths // private static ImageProducer fromRGBDirect2(int pixelSize, int cmpSize, short height, short width, int rowBytes, byte[] pixels) { int bytesPerPixel = pixelSize/8; // Create a mask cmpSize bits wide int mask = 1; mask <<= cmpSize; mask--; int redMask = mask << (cmpSize * 2); int greenMask = mask << cmpSize; int blueMask = mask; ColorModel cm = new DirectColorModel(pixelSize, redMask, greenMask, blueMask); int i = 0; int ii = 0; int row = 0; int[] imagePixels = new int[(rowBytes/bytesPerPixel) * height]; while ( row++ < height ) { for (int j=0; j<rowBytes; j=j+bytesPerPixel) { // Build the Quickdraw pixel from raw bytes int pixel = 0; for (int jj=0; jj<bytesPerPixel; jj++) { pixel <<= 8; pixel |= ((int)pixels[i++]) & 0xFF; } // Copy this value into an java.awt.Image int array imagePixels[ii++] = pixel; } } return new MemoryImageSource(width, height, cm, imagePixels, 0, rowBytes/bytesPerPixel); } private static ImageProducer fromCLUT(int colorTable, int cmpSize, short height, short width, int rowBytes, byte[] pixels) { ColorTableStruct cTab = new ColorTableStruct(colorTable); int ctSize = cTab.getCtSize(); RGBColorStruct rgbColor = new RGBColorStruct(); // Build an indexed color model byte[] reds = new byte[ctSize]; byte[] greens = new byte[ctSize]; byte[] blues = new byte[ctSize]; for (int index=0; index<ctSize; index++) { QuickdrawFunctions.Index2Color(index, rgbColor); reds[index] = (byte)(((int)rgbColor.getRed()) & 0xFF); greens[index] = (byte)(((int)rgbColor.getGreen()) & 0xFF); blues[index] = (byte)(((int)rgbColor.getBlue()) & 0xFF); } ColorModel cm = new IndexColorModel(cmpSize, ctSize, reds, greens, blues); // Move the PICT index values (bytes) into an int array int i = 0; int ii = 0; int row = 0; int[] imagePixels = new int[rowBytes * height]; while ( row++ < height ) for (int j=0; j<rowBytes; j++) imagePixels[ii++] = ((int)pixels[i++]) & 0xFF; return new MemoryImageSource(width, height, cm, imagePixels, 0, rowBytes); } } // Could not find a similar structure wrapper in the JDirect sample code. class ImageStruct extends PointerStruct { short rowBytes; short numRows; public ImageStruct(int inPtr, short inRowBytes, short inNumRows) { super(inPtr); rowBytes = inRowBytes; numRows = inNumRows; } public int getSize() { return rowBytes * numRows; } } // Macintosh Toolbox JDirect /////////////////////////////////////////////////// // // Copy from MRJ JDirect Sample Code files only what is needed. // // NB: During Development include everything by using the files, Quickdraw.java, // QDOffscreen.java, MacTypes.java, and PictUtils.java. Although these files // could be left in the final build they add over 80K to the application. By // extracting out only those structures and methods needed to support this class // this space overhead can be minimized. By using package names this technique // can be used in other places without name conflicts. // class ScrapFunctions implements InterfaceLib { private ScrapFunctions() {} public native static int GetScrap(int hDest, int theType, int [] offset); } interface ErrorConstants { public final short noErr = (short)0; // No error public final short noTypeErr = (short)-102; // No object of that type in scrap } class MemoryFunctions implements InterfaceLib { private MemoryFunctions() {}; public native static int GetHandleSize(int h); public native static int NewHandle(int h); public native static void HLock(int h); public native static void HUnlock(int h); public native static void DisposeHandle(int h); } class QDOffscreenFunctions implements InterfaceLib { private QDOffscreenFunctions() {}; public static short NewGWorld(int [] offscreenGWorld, short PixelDepth, RectStruct boundsRect, ColorTableStruct cTable, GDeviceStruct aGDevice, int flags) { return NewGWorld(offscreenGWorld, PixelDepth, boundsRect.getByteArray(), (cTable != null) ? cTable.getHandle() : 0, (aGDevice != null) ? aGDevice.getHandle() : 0, flags); } public native static short NewGWorld(int [] offscreenGWorld, short PixelDepth, byte[] boundsRect, int cTable, int aGDevice, int flags); public static boolean LockPixels(PixMapStruct pm) { return LockPixels(pm.getHandle()); } public native static boolean LockPixels(int pm); public static void UnlockPixels(PixMapStruct pm) { UnlockPixels(pm.getHandle()); } public native static void UnlockPixels(int pm); public native static void GetGWorld(int [] port, int [] gdh); public static void SetGWorld(int port, GDeviceStruct gdh) { SetGWorld(port, gdh.getHandle()); } public native static void SetGWorld(int port, int gdh); public native static int GetGWorldPixMap(int offscreenGWorld); public static int GetPixBaseAddr(PixMapStruct pm) { return GetPixBaseAddr(pm.getHandle()); } public native static int GetPixBaseAddr(int pm); public native static void DisposeGWorld(int offscreenGWorld); } public interface QuickdrawConstants { public final int RGBDirect = 16; public final int clutType = 0; } class QuickdrawFunctions implements InterfaceLib { private QuickdrawFunctions() {}; public static void DrawPicture(PictureStruct myPicture, RectStruct dstRect) { DrawPicture(myPicture.getHandle(), dstRect.getByteArray()); } public native static void DrawPicture(int myPicture, byte[] dstRect); public static void Index2Color(int index, RGBColorStruct aColor) { Index2Color(index, aColor.getByteArray()); } public native static void Index2Color(int index, byte[] aColor); } // From MacType.java //////////////////////////////////////////////////////////////////////// class RectStruct extends ByteArrayStruct { public final static int sizeOfRect = 8; public RectStruct() { super(sizeOfRect); } public final void setTop(short top) { setShortAt(0, top); } public final void setLeft(short left) { setShortAt(2, left); } public final void setBottom(short bottom) { setShortAt(4, bottom); } public final void setRight(short right) { setShortAt(6, right); } } // From QuickDraw.java //////////////////////////////////////////////////////////////////////// class ColorTableStruct extends HandleStruct { public final static int sizeOfColorTable = 16; public ColorTableStruct(int handle) { super(handle); } public final short getCtSize() { return getShortAt(6); } public int getSize() { return sizeOfColorTable; } } class PixMapStruct extends HandleStruct { public final static int sizeOfPixMap = 50; public PixMapStruct(int handle) { super(handle); } public final short getRowBytes() { return getShortAt(4); } public final short getPixelType() { return getShortAt(30); } public final int getPmTable() { return getIntAt(42);} public final short getPackType() { return getShortAt(16); } public final short getCmpCount() { return getShortAt(34); } public final short getCmpSize() { return getShortAt(36); } public final short getPixelSize() { return getShortAt(32); } public int getSize() { return sizeOfPixMap; } } class GDeviceStruct extends HandleStruct { public GDeviceStruct(int handle) { super(handle); } public final static int sizeOfGDevice = 62; public int getSize() { return sizeOfGDevice; } } class PictureStruct extends HandleStruct { public final static int sizeOfPicture = 10; public PictureStruct(int handle) {super(handle); } public int getSize() { return sizeOfPicture; } } public class RGBColorStruct extends ByteArrayStruct { public RGBColorStruct() { super(sizeOfRGBColor); } public final short getRed() { return getShortAt(0); } public final short getGreen() { return getShortAt(2); } public final short getBlue() { return getShortAt(4); } public final static int sizeOfRGBColor = 6; }