001package arcommon.image; 002 003import java.awt.Component; 004import java.awt.Cursor; 005import java.awt.Graphics; 006import java.awt.Image; 007import java.awt.Point; 008import java.awt.Rectangle; 009import java.awt.Toolkit; 010import java.awt.image.BufferedImage; 011import java.awt.image.ImageObserver; 012import java.awt.image.PixelGrabber; 013import java.awt.image.WritableRaster; 014import java.io.ByteArrayInputStream; 015import java.io.ByteArrayOutputStream; 016import java.io.File; 017import java.io.IOException; 018import java.text.DecimalFormat; 019import java.util.Arrays; 020 021import javax.imageio.IIOException; 022import javax.imageio.ImageIO; 023import javax.imageio.ImageReader; 024import javax.imageio.metadata.IIOMetadata; 025import javax.imageio.metadata.IIOMetadataNode; 026import javax.imageio.stream.ImageInputStream; 027 028import org.w3c.dom.Node; 029 030import arcommon.FileUtil; 031import arcommon.StringUtil; 032import arcommon.Util; 033 034public class ImageUtil { 035 public static int rgbToInt(int r, int g, int b) { 036 return (r << 16) + (g << 8) + b; 037 } 038 039 public static class RGB { 040 public RGB() { 041 ; 042 } 043 044 public RGB(int pred, int pgreen, int pblue) { 045 this.red = pred; 046 this.green = pgreen; 047 this.blue = pblue; 048 if (red < 0 || red > 255) { 049 throw new RuntimeException("red < 0 || red > 255 " + red); 050 } 051 if (green < 0 || green > 255) { 052 throw new RuntimeException("green < 0 || green > 255 " + green); 053 } 054 if (blue < 0 || blue > 255) { 055 throw new RuntimeException("blue < 0 || blue > 255 " + blue); 056 } 057 } 058 059 public int red; 060 public int green; 061 public int blue; 062 063 @Override 064 public String toString() { 065 return "(" + red + ", " + green + ", " + blue + ")"; 066 } 067 } 068 069 public static RGB intToRgb(int pixel) { 070 RGB result = new RGB(); 071// int alpha = (pixel >> 24) & 0xff; 072 result.red = (pixel >> 16) & 0xff; 073 result.green = (pixel >> 8) & 0xff; 074 result.blue = (pixel ) & 0xff; 075 return result; 076 } 077 078 private static DecimalFormat decimalFormat1_2 = new DecimalFormat("0.00"); 079 private static DecimalFormat decimalFormat3_1 = new DecimalFormat("000.0"); 080 081 public static class HCL { 082 public double hue; 083 public double chroma; 084 public double lightness; 085 086 @Override 087 public String toString() { 088 return "(hue=" + decimalFormat3_1.format(hue) + ", chroma=" + decimalFormat1_2.format(chroma) + ", lightness=" + decimalFormat1_2.format(lightness); 089 } 090 } 091 092 public static final double HUE_UNDEFINED = -1.0; 093 094 public static HCL rgbToHcl(int rInt, int gInt, int bInt) { 095 // See https://en.wikipedia.org/wiki/HSV_color_space 096 double r = (double)rInt / 255; 097 double g = (double)gInt / 255; 098 double b = (double)bInt / 255; 099 double maximum = Math.max(r, Math.max(g, b)); 100 double min = Math.min(r, Math.min(g, b)); 101 HCL hcl = new HCL(); 102 hcl.chroma = maximum - min; 103 hcl.lightness = 0.5 * (maximum + min); 104 if (hcl.chroma == 0.0D) { 105 hcl.hue = HUE_UNDEFINED; 106 return hcl; 107 } 108 double hueMark; 109 if (maximum == r) { 110 hueMark = (g - b) / hcl.chroma % 6; 111 } else { 112 if (maximum == g) { 113 hueMark = (b - r) / hcl.chroma + 2; 114 } else { 115 hueMark = (r - g) / hcl.chroma + 4; 116 } 117 } 118 hcl.hue = (hueMark * 60 + 360) % 360; 119 return hcl; 120 } 121 122 public static RGB hclToRgb(HCL hcl) { 123 if (hcl.hue != HUE_UNDEFINED && (hcl.hue < 0.0D || hcl.hue > 360.0D)) { 124 throw new RuntimeException("hcl.hue < 0.0D || hcl.hue > 360.0D " + hcl.hue); 125 } 126 if (hcl.chroma < 0.0D || hcl.chroma > 1.0D) { 127 throw new RuntimeException("hcl.chroma < 0.0D || hcl.chroma > 1.0D " + hcl.chroma); 128 } 129 if (hcl.lightness < 0.0D || hcl.lightness > 1.0D) { 130 throw new RuntimeException("hcl.lightness < 0.0D || hcl.lightness > 1.0D " + hcl.lightness); 131 } 132 double r1, g1, b1; 133 if (hcl.hue == HUE_UNDEFINED) { 134 r1 = 0; 135 g1 = 0; 136 b1 = 0; 137 } else { 138 double hueMark = hcl.hue / 60; 139 double x = hcl.chroma * (1 - Math.abs(hueMark % 2 - 1)); 140 if (hueMark < 0.0D) { 141 throw new RuntimeException("Wrong hue " + hcl.hue); 142 } else if (hueMark < 1.0D) { 143 r1 = hcl.chroma; 144 g1 = x; 145 b1 = 0; 146 } else if (hueMark < 2.0D) { 147 r1 = x; 148 g1 = hcl.chroma; 149 b1 = 0; 150 } else if (hueMark < 3.0D) { 151 r1 = 0; 152 g1 = hcl.chroma; 153 b1 = x; 154 } else if (hueMark < 4.0D) { 155 r1 = 0; 156 g1 = x; 157 b1 = hcl.chroma; 158 } else if (hueMark < 5.0D) { 159 r1 = x; 160 g1 = 0; 161 b1 = hcl.chroma; 162 } else if (hueMark < 6.0D) { 163 r1 = hcl.chroma; 164 g1 = 0; 165 b1 = x; 166 } else { 167 throw new RuntimeException("Wrong hue " + hcl.hue); 168 } 169 } 170 double m = hcl.lightness - 0.5 * hcl.chroma; 171 int r = Math.min((int)((r1 + m) * 255), 255); 172 int g = Math.min((int)((g1 + m) * 255), 255); 173 int b = Math.min((int)((b1 + m) * 255), 255); 174 return new RGB(r, g, b); 175 } 176 177 public static boolean rgbEquals(int rgb1, int rgb2) { 178 return (rgb1 & 0xffffff) == (rgb2 & 0xffffff); 179 } 180 181 public static boolean compareImages(BufferedImage bufferedImage1, BufferedImage bufferedImage2) { 182 if (bufferedImage1.getWidth() != bufferedImage2.getWidth() || bufferedImage1.getHeight() != bufferedImage2.getHeight()) { 183 return false; 184 } 185 int[] pixels1 = imageToPixelArray(bufferedImage1); 186 int[] pixels2 = imageToPixelArray(bufferedImage2); 187 return Arrays.equals(pixels1, pixels2); 188 } 189 190 public static int[] imageToPixelArray(BufferedImage bufferedImage) { 191 int x = 0; 192 int y = 0; 193 int w = bufferedImage.getWidth(); 194 int h = bufferedImage.getHeight(); 195 int[] pixels = new int[w * h]; 196 PixelGrabber pixelGrabber = new PixelGrabber(bufferedImage, x, y, w, h, pixels, 0, w); 197 try { 198 pixelGrabber.grabPixels(); 199 } catch (InterruptedException e) { 200 System.err.println("interrupted waiting for pixels!"); 201 throw new RuntimeException(e); 202 } 203 if ((pixelGrabber.getStatus() & ImageObserver.ABORT) != 0) { 204 System.err.println("Image fetch aborted or failed"); 205 throw new RuntimeException("image fetch aborted or failed"); 206 } 207 return pixels; 208 } 209 210 public static BufferedImage pixelArrayToImage(int[] pixels, int imageWidth, int imageHeight) { 211 int[] tempArray = new int[pixels.length * 3]; 212 int index = 0; 213 for (int i = 0; i < pixels.length; i++) { 214 int pixel = pixels[i]; 215 RGB rgb = intToRgb(pixel); 216 tempArray[index++] = rgb.red; 217 tempArray[index++] = rgb.green; 218 tempArray[index++] = rgb.blue; 219 } 220 221 BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB); 222 WritableRaster raster = image.getRaster(); 223 raster.setPixels(0, 0, imageWidth, imageHeight, tempArray); 224 225 return image; 226 } 227 228 public static final int DEFAULT_DELAY = 75; 229 230 public static class AnimationData { 231 public int numberOfImages; 232 public int[] delays; 233 public Image[] images; 234 public int length; 235 public boolean delayBugFound; 236 237 @Override 238 public String toString() { 239 return "numberOfImages: " + numberOfImages + ", length: " + length + ", delayBugFound: " + delayBugFound + ", delays: " + Arrays.toString(delays); 240 } 241 } 242 243 // Code copied from https://stackoverflow.com/questions/26801433/fix-frame-rate-of-animated-gif-in-java 244 // and modified 245 public static AnimationData getAnimationData(String fileName) { 246 try { 247 ImageReader reader = ImageIO.getImageReadersBySuffix("GIF").next(); 248 File gifFile = new File(fileName); 249 ImageInputStream in = ImageIO.createImageInputStream(gifFile); 250 if (in == null) { 251 if (!FileUtil.exists(fileName)) { 252 throw new RuntimeException("The file \"" + fileName + "\" doesn't exist"); 253 } 254 throw new RuntimeException("in == null for file \"" + fileName + "\""); 255 } 256 reader.setInput(in); 257 258 int numberOfImages = reader.getNumImages(true); 259 260 int minIndex = reader.getMinIndex(); 261 if (minIndex != 0) { 262 System.out.println("minIndex: " + minIndex); 263 } 264 265 IIOMetadata imageMetaData = reader.getImageMetadata(0); 266 String metaFormatName = imageMetaData.getNativeMetadataFormatName(); 267 268 boolean foundBug = false; 269 int length = 0; 270 int[] delays = new int[numberOfImages]; 271 Image[] images = new Image[numberOfImages]; 272 for (int i = 0; i < numberOfImages; i++) { 273 // Get metadata 274 IIOMetadataNode root = (IIOMetadataNode)reader.getImageMetadata(i).getAsTree(metaFormatName); 275 276 try { 277 BufferedImage image = reader.read(i); 278 images[i] = image; 279 } catch (IIOException e) { 280 Util.printMessageWithSourceLine("IIOException" + (e.getMessage() != null ? ": " + e.getMessage() : "")); 281 } catch (ArrayIndexOutOfBoundsException e) { 282 e.getStackTrace(); // Hack to make e.getMessage() work. I have no idea why. 283 Util.printMessageWithSourceLine("ArrayIndexOutOfBoundsException" + (e.getMessage() != null ? ": " + e.getMessage() : "")); 284 } 285 286 // Find GraphicControlExtension node 287 int nNodes = root.getLength(); 288 for (int j = 0; j < nNodes; j++) { 289 Node node = root.item(j); 290 if (node.getNodeName().equalsIgnoreCase("GraphicControlExtension")) { 291 // Get delay value 292 String delayString = ((IIOMetadataNode)node).getAttribute("delayTime"); 293 294 int delay = StringUtil.stringToInt(delayString) * 10; 295 296 // Check if delay is bugged 297 if (delay <= 0) { 298 if (!foundBug) { 299 foundBug = true; 300 Util.printMessageWithSourceLine("foundBug: Delay: " + delay + " " + fileName); 301 } 302 delay = DEFAULT_DELAY; 303 } 304 305 delays[i] = delay; 306 length += delay; 307 308 break; 309 } 310 } 311 } 312 313 AnimationData animationData = new AnimationData(); 314 animationData.numberOfImages = numberOfImages; 315 animationData.delayBugFound = foundBug; 316 animationData.length = length; 317 animationData.delays = delays; 318 animationData.images = images; 319 320 return animationData; 321 } catch (IOException e) { 322 throw new RuntimeException(e); 323 } 324 } 325 326 public static void saveToFile(BufferedImage bufferedImage, String fileName) { 327 saveToFile(bufferedImage, fileName, "gif"); 328 } 329 330 public static void saveToFile(BufferedImage bufferedImage, String fileName, String format) { 331 File outputFile = new File(fileName); 332 try { 333 ImageIO.write(bufferedImage, format, outputFile); 334 } catch (IOException e) { 335 throw new RuntimeException(e); 336 } 337 } 338 339 public static byte[] imageToByte(BufferedImage bufferedImage) { 340 return imageToByte(bufferedImage, "gif"); 341 } 342 343 public static byte[] imageToByte(BufferedImage bufferedImage, String format) { 344 try { 345 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 346 ImageIO.write(bufferedImage, format, byteArrayOutputStream); 347 return byteArrayOutputStream.toByteArray(); 348 } catch (IOException e) { 349 throw new RuntimeException(e); 350 } 351 } 352 353 public static BufferedImage imageFromBytes(byte[] imageData) { 354 try { 355 ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(imageData); 356 BufferedImage bufferedImage = ImageIO.read(byteArrayInputStream); 357 return bufferedImage; 358 } catch (IOException e) { 359 throw new RuntimeException(e); 360 } 361 } 362 363 public static BufferedImage loadFromFile(String fileName) { 364 File inputFile = new File(fileName); 365 try { 366 BufferedImage bufferedImage = ImageIO.read(inputFile); 367 return bufferedImage; 368 } catch (IOException e) { 369 throw new RuntimeException(e); 370 } 371 } 372 373 public static void drawSquare(int[] array, int arrayWidth, int x, int y, int w, int h, int color) { 374 for (int tx = x; tx < x + w; tx++) { 375 array[y * arrayWidth + tx] = color; 376 array[(y + h) * arrayWidth + tx] = color; 377 } 378 379 for (int ty = y; ty < y + h; ty++) { 380 array[ty * arrayWidth + x] = color; 381 array[ty * arrayWidth + x + w - 1] = color; 382 } 383 } 384 385 public static int[] getSubImage(int[] array, int arrayWidth, int x, int y, int w, int h) { 386 int[] result = new int[w * h]; 387 int i = 0; 388 for (int ty = y; ty < y + h; ty++) { 389 for (int tx = x; tx < x + w; tx++) { 390 result[i++] = array[ty * arrayWidth + tx]; 391 } 392 } 393 return result; 394 } 395 396 public static Cursor loadCursor(String fileName) { 397 return loadCursor(fileName, new Point(0, 0), FileUtil.calculateFileNameNoExtension(fileName)); 398 } 399 400 public static Cursor loadCursor(String fileName, Point hotSpot, String name) { 401 // Code copied from http://www.coderanch.com/t/330376/Swing-AWT-SWT-JFace/java/v-use-cursors-are-present 402 try { 403 // Load the toolkit. The toolkit helps you with a lot 404 // of stuff... the toolkit is your friend... [img]http://www.javaranch.com/ubb/wink.gif[/img] 405 Toolkit toolKit = Toolkit.getDefaultToolkit(); 406 // Load the image. 407 Image cursorImage = toolKit.createImage(fileName); 408 // Create the cursor. First arg is the image, 409 // second is the "hotspot", third is it's name. 410 // (Strangely not related to the name parameter 411 // of the getSystemCustomCursor() method... weird.) 412 return toolKit.createCustomCursor(cursorImage, hotSpot, name); 413 } catch (IndexOutOfBoundsException e) { 414 throw new RuntimeException(e); 415 } 416 } 417 418 public static BufferedImage componentToImage(Component component) { 419 Rectangle bounds = component.getBounds(); 420 return componentToImage(component, bounds); 421 } 422 423 public static BufferedImage componentToImage(Component component, Rectangle rectangle) { 424 BufferedImage image = new BufferedImage(component.getWidth(), component.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE); 425 Graphics g = image.getGraphics(); 426 g.setColor(component.getForeground()); 427 g.setFont(component.getFont()); 428 component.paintAll(g); 429 if (rectangle == null) { 430 rectangle = new Rectangle(0, 0, component.getWidth(), component.getHeight()); 431 } 432 return image.getSubimage(rectangle.x, rectangle.y, rectangle.width, rectangle.height); 433 } 434}