/* * Copyright (C) 2007, 2008 Quadduc <quadduc@gmail.com> * Copyright (C) 2007, 2011 IsmAvatar <IsmAvatar@gmail.com> * Copyright (C) 2007 Clam <clamisgood@gmail.com> * Copyright (C) 2013, 2014 Robert B. Colton * * This file is part of LateralGM. * LateralGM is free software and comes with ABSOLUTELY NO WARRANTY. * See LICENSE for details. */ package org.lateralgm.main; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.image.BufferedImage; import java.awt.image.FilteredImageSource; import java.awt.image.ImageFilter; import java.awt.image.ImageProducer; import java.awt.image.RGBImageFilter; import java.awt.image.WritableRaster; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.plugins.bmp.BMPImageWriteParam; import javax.imageio.spi.IIORegistry; import javax.imageio.stream.FileImageOutputStream; import javax.imageio.stream.ImageInputStream; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.SwingUtilities; import org.lateralgm.components.CustomFileChooser; import org.lateralgm.components.impl.CustomFileFilter; import org.lateralgm.components.visual.FileChooserImagePreview; import org.lateralgm.file.ApngIO; import org.lateralgm.file.iconio.BitmapDescriptor; import org.lateralgm.file.iconio.ICOFile; import org.lateralgm.file.iconio.ICOImageReaderSPI; import org.lateralgm.file.iconio.WBMPImageReaderSpiFix; import org.lateralgm.messages.Messages; import org.lateralgm.resources.Resource; import org.lateralgm.resources.ResourceReference; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.sun.imageio.plugins.wbmp.WBMPImageReaderSpi; public final class Util { private static final InvokeOnceRunnable IOR = new InvokeOnceRunnable(); private Util() { } public static CustomFileChooser imageReadFc = null; public static CustomFileChooser imageWriteFc = null; public static void tweakIIORegistry() { IIORegistry reg = IIORegistry.getDefaultInstance(); reg.registerServiceProvider(new ICOImageReaderSPI()); reg.deregisterServiceProvider(reg.getServiceProviderByClass(WBMPImageReaderSpi.class)); reg.registerServiceProvider(new WBMPImageReaderSpiFix()); } public static BufferedImage paintBackground(int width, int height, Color background, Color foreground) { if (width < 1) width = 1; if (height < 1) height = 1; BufferedImage dest = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB); for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { if ((row + col) % 2 == 0) { dest.setRGB(col,row,foreground.getRGB()); } else { dest.setRGB(col,row,background.getRGB()); } } } return dest; } public static BufferedImage paintBackground(int width, int height) { return paintBackground(width,height,new Color(Prefs.imagePreviewBackgroundColor),new Color( Prefs.imagePreviewForegroundColor)); } public static BufferedImage paintBackgroundScaled(int width, int height, int TILE, Color background, Color foreground) { BufferedImage dest = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB); Graphics2D g = dest.createGraphics(); g.setClip(0,0,width,height); g.setColor(background); g.fillRect(0,0,width,height); g.setColor(foreground); int w = width / TILE + 1; int h = height / TILE + 1; for (int row = 0; row < h; row++) { for (int col = 0; col < w; col++) { if ((row + col) % 2 == 0) { g.fillRect(col * TILE,row * TILE,TILE,TILE); } } } return dest; } public static BufferedImage paintBackgroundScaled(int width, int height, int TILE) { return paintBackgroundScaled(width,height,TILE,new Color(Prefs.imagePreviewBackgroundColor), new Color(Prefs.imagePreviewForegroundColor)); } public static String urlEncode(String s) { try { return URLEncoder.encode(s,"UTF-8"); } catch (UnsupportedEncodingException e) { throw new Error(e); } } public static String urlDecode(String s) { try { return URLDecoder.decode(s,"UTF-8"); } catch (UnsupportedEncodingException e) { throw new Error(e); } } public static ByteArrayOutputStream readFully(InputStream in) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[4096]; // Read in the bytes int numRead = 0; while ((numRead = in.read(buffer)) >= 0) baos.write(buffer,0,numRead); // Close the input stream and return bytes return baos; } public static Rectangle stringToRectangle(String s, Rectangle defaultValue) { if (s == null) return defaultValue; String[] sa = s.split(" +"); if (sa.length != 4) return defaultValue; int[] ia = new int[4]; for (int i = 0; i < 4; i++) try { ia[i] = Integer.parseInt(sa[i]); } catch (NumberFormatException e) { return defaultValue; } return new Rectangle(ia[0],ia[1],ia[2],ia[3]); } public static String rectangleToString(Rectangle r) { return String.format("%d %d %d %d",r.x,r.y,r.width,r.height); } // this is the size on disc in kibibytes public static String formatDataSize(long bytes) { if (bytes <= 0) return "0 B"; final String[] units = new String[] { "B","KB","MB","GB","TB" }; int digits = (int) (Math.log(bytes) / Math.log(1024)); return new DecimalFormat("#,##0.##").format(bytes / Math.pow(1024,digits)) + " " + units[digits]; } // this is the size that Studio will report in kilobytes public static String formatDataSizeAlt(long bytes) { if (bytes <= 0) return "0 B"; final String[] units = new String[] { "B","KB","MB","GB","TB" }; int digits = (int) (Math.log(bytes) / Math.log(1000)); return new DecimalFormat("#,##0.##").format(bytes / Math.pow(1000,digits)) + " " + units[digits]; } public static BufferedImage toBufferedImage(Image image) { if (image instanceof BufferedImage) return (BufferedImage) image; // This code ensures that all the pixels in the image are loaded image = new ImageIcon(image).getImage(); BufferedImage bimage = new BufferedImage(image.getWidth(null),image.getHeight(null), BufferedImage.TYPE_INT_ARGB); Graphics g = bimage.createGraphics(); g.drawImage(image,0,0,null); g.dispose(); return bimage; } public static BufferedImage getTransparentImage(BufferedImage i) { if (i == null) return null; final int t = i.getRGB(0,i.getHeight() - 1) & 0x00FFFFFF; ImageFilter filter = new RGBImageFilter() { @Override public int filterRGB(int x, int y, int rgb) { if ((rgb & 0x00FFFFFF) == t) return t; return rgb; } }; ImageProducer ip = new FilteredImageSource(i.getSource(),filter); return toBufferedImage(Toolkit.getDefaultToolkit().createImage(ip)); } private static void createImageReadChooser() { imageReadFc = new CustomFileChooser("/org/lateralgm","LAST_IMAGE_DIR"); imageReadFc.setAccessory(new FileChooserImagePreview(imageReadFc)); String[] readexts = { "apng","gif" }; if (LGM.javaVersion >= 10600) { String[] internalexts = ImageIO.getReaderFileSuffixes(); ArrayList<String> extensions = new ArrayList<String>(); for (String ext : readexts) { extensions.add(ext); } for (String ext : internalexts) { if (!extensions.contains(ext)) { extensions.add(ext); } } readexts = extensions.toArray(new String[extensions.size()]); } for (int i = 0; i < readexts.length; i++) readexts[i] = "." + readexts[i]; //$NON-NLS-1$ String allSpiImages = Messages.getString("Util.ALL_SPI_IMAGES"); //$NON-NLS-1$ CustomFileFilter allSpiFilter = new CustomFileFilter(allSpiImages,readexts); imageReadFc.addChoosableFileFilter(allSpiFilter); for (String element : readexts) { imageReadFc.addChoosableFileFilter(new CustomFileFilter(Messages.format("Util.FILES", //$NON-NLS-1$ element),element)); } imageReadFc.setFileFilter(allSpiFilter); } private static void createImageWriteChooser() { imageWriteFc = new CustomFileChooser("/org/lateralgm","LAST_IMAGE_DIR"); String[] writeexts = { "apng" }; if (LGM.javaVersion >= 10600) { String[] internalexts = ImageIO.getWriterFileSuffixes(); ArrayList<String> extensions = new ArrayList<String>(); for (String ext : writeexts) { extensions.add(ext); } for (String ext : internalexts) { if (!extensions.contains(ext)) { extensions.add(ext); } } writeexts = extensions.toArray(new String[extensions.size()]); } for (int i = 0; i < writeexts.length; i++) writeexts[i] = "." + writeexts[i]; //$NON-NLS-1$ String allSpiImages = Messages.getString("Util.ALL_SPI_IMAGES"); //$NON-NLS-1$ CustomFileFilter allSpiFilter = new CustomFileFilter(allSpiImages,writeexts); imageWriteFc.addChoosableFileFilter(allSpiFilter); for (String element : writeexts) { imageWriteFc.addChoosableFileFilter(new CustomFileFilter(Messages.format("Util.FILES", //$NON-NLS-1$ element),element)); } imageWriteFc.setFileFilter(allSpiFilter); } /** * Shows a JFileChooser with file filters for all currently registered instances of * ImageReaderSpi with multiple file selection. * * @return The selected image, or null if one is not chosen */ public static File chooseImageFile() { if (imageReadFc == null) { createImageReadChooser(); } imageReadFc.setMultiSelectionEnabled(false); if (imageReadFc.showOpenDialog(LGM.frame) == JFileChooser.APPROVE_OPTION) return imageReadFc.getSelectedFile(); return null; } /** * Shows a JFileChooser with file filters for all currently registered instances of * ImageReaderSpi with multiple file selection. * * @return The selected image, or null if one is not chosen */ public static File[] chooseImageFiles() { if (imageReadFc == null) { createImageReadChooser(); } imageReadFc.setMultiSelectionEnabled(true); if (imageReadFc.showOpenDialog(LGM.frame) == JFileChooser.APPROVE_OPTION) return imageReadFc.getSelectedFiles(); return null; } /** * Returns the selected file from a JFileChooser, including the extension from * the file filter. */ public static File getSelectedFileWithExtension(JFileChooser c) { File file = c.getSelectedFile(); if (c.getFileFilter() instanceof CustomFileFilter) { String[] exts = ((CustomFileFilter) c.getFileFilter()).getExtensions(); String nameLower = file.getName().toLowerCase(); for (String ext : exts) { // check if it already has a valid extension if (ext.startsWith(".")) { ext = ext.substring(1); } if (nameLower.endsWith('.' + ext.toLowerCase())) { return file; // if yes, return as-is } } // if not, append the first extension from the selected filter file = new File(file.toString() + (exts[0].startsWith(".") ? "" : ".") + exts[0]); } return file; } private static String getFileExtension(File file) { String name = file.getName(); int lastIndexOf = name.lastIndexOf("."); if (lastIndexOf == -1) { return ""; // empty extension } return name.substring(lastIndexOf + 1); } //TODO: JPEG Writing is bugged in some newer JVM versions causing the RGB color channels to be mixed up. // This is a bug Oracle claims to have fixed but they actually haven't. // http://bugs.java.com/view_bug.do?bug_id=4712797 // http://bugs.java.com/view_bug.do?bug_id=4776576 // http://stackoverflow.com/questions/13072312/jpeg-image-color-gets-drastically-changed-after-just-imageio-read-and-imageio public static void writeImageQualityPrompt(BufferedImage img, String ext, File f) throws FileNotFoundException,IOException { Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName(ext); ImageWriter writer = (ImageWriter) iter.next(); BMPImageWriteParam iwp = (BMPImageWriteParam) writer.getDefaultWriteParam(); if (iwp.canWriteCompressed()) { iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); for (String str : iwp.getCompressionTypes()) { System.out.println(str); } if (ext.equals("jpg") || ext.equals("jpeg")) { iwp.setCompressionQuality(1.0f); } else if (ext.equals("bmp")) { iwp = (BMPImageWriteParam) iwp; //iwp.setCompressionType("BI_BITFIELDS"); iwp.setCompressionType("BI_RLE4"); //iwp.setCompressionType("BI_RLE8"); //iwp.setCompressionType("BI_RGB"); } } FileImageOutputStream output = new FileImageOutputStream(f); writer.setOutput(output); IIOImage image = new IIOImage(img,null,null); writer.write(null,image,iwp); writer.dispose(); output.close(); } public static BufferedImage convertImage(BufferedImage img, int format, Color col) { BufferedImage bi = new BufferedImage(img.getWidth(),img.getHeight(),format); Graphics gd = bi.getGraphics(); if (col != null) { gd.setColor(col); gd.fillRect(0,0,img.getWidth(),img.getHeight()); } gd.drawImage(img,0,0,img.getWidth(),img.getHeight(),null); gd.dispose(); return bi; } public static BufferedImage convertImage(BufferedImage img, int format) { return convertImage(img,format,null); } public static BufferedImage clearBackground(BufferedImage img, Color col) { return convertImage(img,BufferedImage.TYPE_INT_ARGB,col); } public static BufferedImage makeOpaque(BufferedImage img, Color col) { return convertImage(img,BufferedImage.TYPE_BYTE_INDEXED,col); } public static void saveImages(ArrayList<BufferedImage> imgs) { if (imgs == null || imgs.size() <= 0) { JOptionPane.showMessageDialog(LGM.frame,Messages.getString("Util.NO_IMAGE_MESSAGE"), Messages.getString("Util.NO_IMAGE_TITLE"),JOptionPane.WARNING_MESSAGE); return; } if (imageWriteFc == null) { createImageWriteChooser(); } imageWriteFc.setMultiSelectionEnabled(false); if (imageWriteFc.showSaveDialog(LGM.frame) == JFileChooser.APPROVE_OPTION) { try { File f = getSelectedFileWithExtension(imageWriteFc); String ext = getFileExtension(f); if (ext.equals("apng")) { FileOutputStream os = new FileOutputStream(f); ApngIO.imagesToApng(imgs,os); os.close(); } else { // BMP and other formats can not write alpha transparent images, so if writing fails // try to remove the alpha layer and then write if (!ImageIO.write(imgs.get(0),ext,f)) { BufferedImage bi = makeOpaque(imgs.get(0),Color.white); ImageIO.write(bi,ext,f); } } } catch (IOException e) { LGM.showDefaultExceptionHandler(e); } } } public static void saveImage(BufferedImage img) { if (img == null) { JOptionPane.showMessageDialog(LGM.frame,Messages.getString("Util.NO_IMAGE_MESSAGE"), Messages.getString("Util.NO_IMAGE_TITLE"),JOptionPane.WARNING_MESSAGE); return; } if (imageWriteFc == null) { createImageWriteChooser(); } imageWriteFc.setMultiSelectionEnabled(false); if (imageWriteFc.showSaveDialog(LGM.frame) == JFileChooser.APPROVE_OPTION) { try { File f = getSelectedFileWithExtension(imageWriteFc); String ext = getFileExtension(f); if (ext.equals("apng")) { ArrayList<BufferedImage> imgs = new ArrayList<BufferedImage>(1); imgs.add(img); FileOutputStream os = new FileOutputStream(f); ApngIO.imagesToApng(imgs,os); os.close(); //} else if (ext.equalsIgnoreCase("ico")) { //new ICOFile(img.getRGB(startX,startY,w,h,rgbArray,offset,scansize)).write(new FileOutputStream(f)); } else { // BMP and other formats can not write alpha transparent images, so if writing fails // try to remove the alpha layer and then write if (!ImageIO.write(img,ext,f)) { BufferedImage bi = makeOpaque(img,Color.white); ImageIO.write(bi,ext,f); } } } catch (IOException e) { LGM.showDefaultExceptionHandler(e); } } } public static byte[] readBinaryFile(String path) { File file = new File(getPOSIXPath(path)); byte[] fileData = new byte[(int) file.length()]; DataInputStream dis = null; try { dis = new DataInputStream(new FileInputStream(file)); dis.readFully(fileData); } catch (IOException e) { e.printStackTrace(); JOptionPane.showMessageDialog(LGM.frame,"There was an issue opening a data input stream.", "Read Error",JOptionPane.ERROR_MESSAGE); } finally { try { if (dis != null) { dis.close(); } } catch (IOException e) { e.printStackTrace(); JOptionPane.showMessageDialog(LGM.frame,"There was an issue closing a data input stream.", "Read Error",JOptionPane.ERROR_MESSAGE); } } return fileData; } public static void writeBinaryFile(String path, byte[] data) throws IOException { BufferedOutputStream bos = null; FileOutputStream fos = null; try { //create an object of FileOutputStream fos = new FileOutputStream(new File(Util.getPOSIXPath(path))); //create an object of BufferedOutputStream bos = new BufferedOutputStream(fos); bos.write(data); } finally { bos.close(); fos.close(); } } public static String getPOSIXPath(String path) { return path.replace("\\","/"); } private static ArrayList<BufferedImage> readGIF(File gif) throws IOException { ArrayList<BufferedImage> frames = new ArrayList<BufferedImage>(0); ImageReader reader = ImageIO.getImageReadersByFormatName("gif").next(); ; reader.setInput(ImageIO.createImageInputStream(gif)); int width = -1; int height = -1; IIOMetadata metadata = reader.getStreamMetadata(); if (metadata != null) { IIOMetadataNode globalRoot = (IIOMetadataNode) metadata.getAsTree(metadata.getNativeMetadataFormatName()); NodeList globalScreenDescriptor = globalRoot.getElementsByTagName("LogicalScreenDescriptor"); if (globalScreenDescriptor != null && globalScreenDescriptor.getLength() > 0) { IIOMetadataNode screenDescriptor = (IIOMetadataNode) globalScreenDescriptor.item(0); if (screenDescriptor != null) { width = Integer.parseInt(screenDescriptor.getAttribute("logicalScreenWidth")); height = Integer.parseInt(screenDescriptor.getAttribute("logicalScreenHeight")); } } } BufferedImage master = null; Graphics2D masterGraphics = null; for (int frameIndex = 0;; frameIndex++) { BufferedImage image; try { image = reader.read(frameIndex); } catch (IndexOutOfBoundsException io) { break; } if (width == -1 || height == -1) { width = image.getWidth(); height = image.getHeight(); } IIOMetadataNode root = (IIOMetadataNode) reader.getImageMetadata(frameIndex).getAsTree( "javax_imageio_gif_image_1.0"); IIOMetadataNode gce = (IIOMetadataNode) root.getElementsByTagName("GraphicControlExtension").item( 0); //int delay = Integer.valueOf(gce.getAttribute("delayTime")); String disposal = gce.getAttribute("disposalMethod"); int x = 0; int y = 0; if (master == null) { master = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB); masterGraphics = master.createGraphics(); masterGraphics.setBackground(new Color(0,0,0,0)); } else { NodeList children = root.getChildNodes(); for (int nodeIndex = 0; nodeIndex < children.getLength(); nodeIndex++) { Node nodeItem = children.item(nodeIndex); if (nodeItem.getNodeName().equals("ImageDescriptor")) { NamedNodeMap map = nodeItem.getAttributes(); x = Integer.valueOf(map.getNamedItem("imageLeftPosition").getNodeValue()); y = Integer.valueOf(map.getNamedItem("imageTopPosition").getNodeValue()); } } } masterGraphics.drawImage(image,x,y,null); BufferedImage copy = new BufferedImage(master.getColorModel(),master.copyData(null), master.isAlphaPremultiplied(),null); frames.add(copy); if (disposal.equals("restoreToPrevious")) { BufferedImage from = null; for (int i = frameIndex - 1; i >= 0; i--) { if (frameIndex == 0) { from = frames.get(i); break; } } master = new BufferedImage(from.getColorModel(),from.copyData(null), from.isAlphaPremultiplied(),null); masterGraphics = master.createGraphics(); masterGraphics.setBackground(new Color(0,0,0,0)); } else if (disposal.equals("restoreToBackgroundColor")) { masterGraphics.clearRect(x,y,image.getWidth(),image.getHeight()); } } masterGraphics.dispose(); reader.dispose(); return frames; } public static BufferedImage getValidImage() { File f = chooseImageFile(); if (f == null || !f.exists()) return null; try { if (f.getName().endsWith(".gif")) { return readGIF(f).get(0); } else if (f.getName().endsWith(".apng")) { FileInputStream is = new FileInputStream(f); List<BufferedImage> imgs = ApngIO.apngToBufferedImages(is); is.close(); return imgs.get(0); } else { return ImageIO.read(f); } } catch (IOException e) { LGM.showDefaultExceptionHandler(e); } return null; } public static BufferedImage[] getValidImages() { File[] f = chooseImageFiles(); if (f == null) return null; try { ArrayList<BufferedImage> subframes = new ArrayList<BufferedImage>(0); for (int i = 0; i < f.length; i++) { if (!f[i].exists()) continue; if (f[i].getName().endsWith(".gif")) { subframes.addAll(readGIF(f[i])); } else if (f[i].getName().endsWith(".apng")) { FileInputStream is = new FileInputStream(f[i]); subframes.addAll(ApngIO.apngToBufferedImages(is)); is.close(); } else if (f[i].getName().endsWith(".ico")) { List<BufferedImage> imgs = new ICOFile(readBinaryFile(f[i].getPath())).getImages(); return imgs.toArray(new BufferedImage[imgs.size()]); } else { subframes.add(ImageIO.read(f[i])); } } return subframes.toArray(new BufferedImage[0]); } catch (Exception e) { LGM.showDefaultExceptionHandler(e); } return null; } public static BufferedImage[] getValidImages(ImageInputStream in) throws IOException, IllegalArgumentException { Iterator<ImageReader> it = ImageIO.getImageReaders(in); ImageReader reader = it.next(); reader.setInput(in); int count = reader.getNumImages(true); BufferedImage[] img = new BufferedImage[count]; for (int i = 0; i < count; i++) img[i] = reader.read(i); //TODO: Gif overlay support (as GM already does) return img; } public static BufferedImage cloneImage(BufferedImage bi) { if (bi == null) return null; //clone the raster WritableRaster or = bi.getRaster(); WritableRaster nr = or.createCompatibleWritableRaster(); nr.setRect(or); //construct with cloned raster, assume it has no special properties return new BufferedImage(bi.getColorModel(),or,bi.isAlphaPremultiplied(),null); } public static Color convertGmColor(int col) { return new Color(col & 0xFF,(col & 0xFF00) >> 8,(col & 0xFF0000) >> 16); } public static Color convertGmColorWithAlpha(int col) { return new Color(col & 0xFF,(col & 0xFF00) >> 8,(col & 0xFF0000) >> 16, (col & 0xFF000000) >>> 24); } public static Color convertInstanceColorWithAlpha(int col) { return new Color((col & 0xFF0000) >> 16,(col & 0xFF00) >> 8,col & 0xFF, (col & 0xFF000000) >>> 24); } public static Color HSL2RGB(float h, float s, float l, int a) { float c = (1 - Math.abs(2.f * l - 1.f)) * s; float h_ = h / 60.f; float h_mod2 = h_; if (h_mod2 >= 4.f) h_mod2 -= 4.f; else if (h_mod2 >= 2.f) h_mod2 -= 2.f; float x = c * (1 - Math.abs(h_mod2 - 1)); float r_, g_, b_; if (h_ < 1) { r_ = c; g_ = x; b_ = 0; } else if (h_ < 2) { r_ = x; g_ = c; b_ = 0; } else if (h_ < 3) { r_ = 0; g_ = c; b_ = x; } else if (h_ < 4) { r_ = 0; g_ = x; b_ = c; } else if (h_ < 5) { r_ = x; g_ = 0; b_ = c; } else { r_ = c; g_ = 0; b_ = x; } float m = l - (0.5f * c); int r = (int) ((r_ + m) * (255.f) + 0.5f); int g = (int) ((g_ + m) * (255.f) + 0.5f); int b = (int) ((b_ + m) * (255.f) + 0.5f); return new Color(r,g,b,a); } public static void RGB2HSL(int red, int green, int blue, float[] hslvals) { float r = red / 255.f; float g = green / 255.f; float b = blue / 255.f; float max = Math.max(Math.max(r,g),b); float min = Math.min(Math.min(r,g),b); float c = max - min; float h_ = 0.f; if (c == 0) { h_ = 0; } else if (max == r) { h_ = (float) (g - b) / c; if (h_ < 0) h_ += 6.f; } else if (max == g) { h_ = (float) (b - r) / c + 2.f; } else if (max == b) { h_ = (float) (r - g) / c + 4.f; } float h = 60.f * h_; float l = (max + min) * 0.5f; float s; if (c == 0) { s = 0.f; } else { s = c / (1 - Math.abs(2.f * l - 1.f)); } hslvals[0] = h; hslvals[1] = s; hslvals[2] = l; } public static int getGmColor(Color col) { return col.getRed() | col.getGreen() << 8 | col.getBlue() << 16; } public static int getGmColorWithAlpha(Color col) { return col.getRed() | col.getGreen() << 8 | col.getBlue() << 16 | col.getAlpha() << 24; } public static int getGmColorWithAlpha(Color col, int alpha) { return col.getRed() | col.getGreen() << 8 | col.getBlue() << 16 | alpha << 24; } /** Turns an AWT Java Color into an rgba(r,g,b,a) CSS formatted string. * NOTE: Java's 1.8 HTML implementation does not support opacity. * * @param col The integer value of the color. * @param hasTransparency Whether the integer color value has transparency. * @return The CSS encoded string representing the color value. */ public static String getHTMLColor(int col, boolean hasTransparency) { if (hasTransparency) { return String.format("rgba(%d,%d,%d,%d)",col >> 16 & 0xFF,col >> 8 & 0xFF,col & 0xFF, col >> 24 & 0xFF); } return String.format("rgb(%d,%d,%d)",col >> 16 & 0xFF,col >> 8 & 0xFF,col & 0xFF); } /** Turns an AWT Java Color into an rgba(r,g,b,a) CSS formatted string. * NOTE: Java's 1.8 HTML implementation does not support opacity. * * @param col The integer value of the color. * @param hasTransparency Whether the integer color value has transparency. * @return The CSS encoded string representing the color value. */ public static String getHTMLColor(Color col, boolean hastransparency) { if (hastransparency) { return String.format("rgba(%d,%d,%d,%d)",col.getRed(),col.getGreen(),col.getBlue(), col.getAlpha()); } return String.format("rgb(%d,%d,%d)",col.getRed(),col.getGreen(),col.getBlue()); } /** Formats the given color to a hexadecimal encoded string. * * @param col The color value that should be formatted to a hexadecimal string. * @param includeAlpha Whether the alpha of the color value should be formated. * @return The hexadecimal encoded string for the color. */ public static String formatColortoHex(Color col, boolean includeAlpha) { if (includeAlpha) { return String.format("%02X%02X%02X%02X", col.getRed(), col.getGreen(), col.getBlue(), col.getAlpha()); } return String.format("%02X%02X%02X", col.getRed(), col.getGreen(), col.getBlue()); } /** Formats the given color to a hexadecimal encoded string. * * @param col The color value that should be formatted to a hexadecimal string. * @return The hexadecimal encoded string for the color. */ public static String formatColortoHex(Color col) { return String.format("%02X%02X%02X%02X", col.getRed(), col.getGreen(), col.getBlue(), col.getAlpha()); } public static long getInstanceColorWithAlpha(Color col, int alpha) { return (alpha << 24 | col.getRed() << 16 | col.getGreen() << 8 | col.getBlue()) & 0xFFFFFFFFL; } public static Component addDim(Container container, Component comp, int width, int height) { comp.setPreferredSize(new Dimension(width,height)); return container.add(comp); } public static JPanel makeRadioPanel(String paneTitle, int width, int height) { JPanel panel = makeTitledPanel(paneTitle,width,height); panel.setLayout(new BoxLayout(panel,BoxLayout.Y_AXIS)); return panel; } public static JPanel makeTitledPanel(String paneTitle, int width, int height) { JPanel panel = new JPanel(); panel.setBorder(BorderFactory.createTitledBorder(paneTitle)); Dimension newSize = new Dimension(width,height); panel.setPreferredSize(newSize); panel.setMaximumSize(newSize); panel.setMinimumSize(newSize); return panel; } public static <R extends Resource<R,?>>R deRef(ResourceReference<R> ref) { return ref == null ? null : ref.get(); } /** * Flags a class as inherently unique (when not cloned for modification), * indicating that it has an isEqual(E) method for comparing fields, * rather than the equals() method, which falls back to Object.equals. * @param <E> The other class to compare fields with. Typically, this * is the implementing class. */ public static interface InherentlyUnique<E extends InherentlyUnique<E>> { /** * Objects which are inherently unique (when not cloned for modification) * can't compare fields in their equals() method. As such, we instead * use our own method, isEqual, which compares fields for an equality check. * @return Whether the fields of these actions are equal. */ boolean isEqual(E other); } public static <V extends InherentlyUnique<V>>boolean areInherentlyUniquesEqual(List<V> a, List<V> b) { if (a == b) return true; if (a == null || b == null) return false; ListIterator<V> e1 = a.listIterator(); ListIterator<V> e2 = b.listIterator(); while (e1.hasNext() && e2.hasNext()) { V o1 = e1.next(); V o2 = e2.next(); if (!(o1 == null ? o2 == null : o1.isEqual(o2))) return false; } return !(e1.hasNext() || e2.hasNext()); } public static int gcd(int a, int b) { while (b != 0) { int c = a % b; a = b; b = c; } return a; } /** * Integer division with rounding towards negative infinity. */ public static int negDiv(int a, int b) { return a >= 0 ? a / b : ~(~a / b); } public static void invokeOnceLater(Runnable r) { IOR.add(r); } private static class InvokeOnceRunnable implements Runnable { private final ArrayList<Runnable> queue = new ArrayList<Runnable>(); private boolean inDispatcher = false; public synchronized void add(Runnable r) { if (queue.contains(r)) return; queue.add(r); if (!inDispatcher) { SwingUtilities.invokeLater(this); inDispatcher = true; } } public void run() { Runnable[] q; synchronized (this) { inDispatcher = false; q = new Runnable[queue.size()]; q = queue.toArray(q); queue.clear(); } for (Runnable r : q) r.run(); } } /** * Makes an icon suitable for embedding into a GM runner * of the given version. Before the compilation process was improved, * the icon was written by overwriting a placeholder of fixed size. * Any icon greater than this size (32x32@32bpp) will usually overflow * onto the resource table of the exe, causing a crash. If required, * this function will do its best to choose the image with the best resolution * and colour depth possible, discarding all the other images. * * @param ico the icon to (possibly) modify * @param ver the version to make the icon suitable for */ public static void fixIcon(ICOFile ico, int ver) { //Preference weighting: //32x32 = 3, 16x16 = 1, anything else = -9 //32bpp = 3, 24bpp = 2, 8bpp = 1, >0 bpp = 0, anything else = -9 if (ver < 800) { byte[] weights = new byte[ico.getImageCount()]; int i = 0; for (BitmapDescriptor bmd : ico.getDescriptors()) { int width = bmd.getWidth(); if (width == 32) weights[i] += 3; else if (width == 16) weights[i]++; else weights[i] -= 9; int bpp = bmd.getBPP(); if (bpp == 32) weights[i] += 3; if (bpp == 24) weights[i] += 2; if (bpp == 8) weights[i]++; else if (bpp <= 0) weights[i] -= 9; i++; } int maxind = 0; int maxweight = 0; for (i = 0; i < weights.length; i++) if (weights[i] > maxweight) { maxweight = weights[i]; maxind = i; } BitmapDescriptor bmd = ico.getDescriptor(maxind); ico.getDescriptors().clear(); ico.getDescriptors().add(bmd); } } }