/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package edu.mit.csail.sdg.alloy4;
import java.io.IOException;
import java.io.File;
import java.io.RandomAccessFile;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
/** Graphical convenience methods for producing PNG files. */
public final strictfp class OurPNGWriter {
/** The constructor is private, since this utility class never needs to be instantiated. */
private OurPNGWriter () { }
/** Writes the image as a PNG file with the given horizontal and vertical dots-per-inch. */
public static void writePNG (BufferedImage image, String filename, double dpiX, double dpiY) throws IOException {
try {
ImageIO.write(image, "PNG", new File(filename)); // some versions of Java sometimes throws an exception during saving...
setDPI(filename, dpiX, dpiY);
} catch(Throwable ex) {
if (ex instanceof IOException) throw (IOException)ex;
if (ex instanceof StackOverflowError) throw new IOException("Out of memory trying to save the PNG file to " + filename);
if (ex instanceof OutOfMemoryError) throw new IOException("Out of memory trying to save the PNG file to " + filename);
throw new IOException("Error writing the PNG file to " + filename + " (" + ex + ")");
}
}
/* PNG consists of a "8 byte header" followed by one or more CHUNK...
*
* Each CHUNK:
* ===========
* 4 bytes: an integer N expressed with most-significant-byte first
* 4 bytes: Chunk Type
* N bytes: Chunk Data
* 4 bytes: Checksum (this checksum is computed over the Chunk Type and Chunk Data)
*
* Each PNG must contain an IDAT chunk (this is the actual pixels of the image)
*
* Each PNG may contain an optional pHYs chunk that describes the horizontal and vertical dots-per-meter information.
* If such a chunk exists, it must come before (though not necessarily immediately before) the IDAT chunk.
*
* pHYs CHUNK:
* ===========
* 4 bytes: 0 , 0 , 0 , 9
* 4 bytes: 'p' , 'H' , 'Y' , 's'
* 4 bytes: horizontal dots per meter (most-significant-byte first)
* 4 bytes: vertical dots per meter (most-significant-byte first)
* 1 byte: 1
* 4 bytes: Checksum
*/
/** Modifies the given PNG file to have the given horizontal and vertical dots-per-inch. */
private static void setDPI (String filename, double dpiX, double dpiY) throws IOException {
RandomAccessFile f = null;
try {
f = new RandomAccessFile(filename, "rw");
for(long total=f.length(), pos=8; pos<total;) { // Jump to the right place for inserting the pHYs chunk, then insert it
f.seek(pos);
int a1 = f.read(); if (a1 < 0) break;
int a2 = f.read(); if (a2 < 0) break;
int a3 = f.read(); if (a3 < 0) break;
int a4 = f.read(); if (a4 < 0) break;
int b1 = f.read(); if (b1 < 0) break;
int b2 = f.read(); if (b2 < 0) break;
int b3 = f.read(); if (b3 < 0) break;
int b4 = f.read(); if (b4 < 0) break;
int jump = (a1<<24) | (a2<<16) | (a3<<8) | a4;
if (jump<0 || (total-pos)-12<jump) throw new IOException("PNG chunk size exceeds the rest of the file.");
if ((b1=='I' && b2=='D' && b3=='A' && b4=='T') || (b1=='p' && b2=='H' && b3=='Y' && b4=='s')) {
Util.shift(f, (b1=='p' ? (pos+12+jump) : pos), pos+21); // skip over the existing pHYs chunk if we see it
f.seek(pos);
writeDPI(f, dpiX, dpiY);
f.close();
f = null;
return; // use a "return" rather than "break" so that we don't enter the line that throws the IOException
}
pos = pos + 12 + jump;
}
throw new IOException("PNG is missing the IDAT chunk");
} finally {
Util.close(f);
}
}
/** Write a "pHYs" chunk into the given file with the given horizontal and vertical dots-per-inch. */
private static void writeDPI (RandomAccessFile file, double dpiX, double dpiY) throws IOException {
int x = (int) (dpiX/0.0254), y = (int) (dpiY/0.0254); // Translate dots-per-inch into dots-per-meter
writeChunk(file, new int[] {'p', 'H', 'Y', 's', x>>>24, x>>>16, x>>>8, x, y>>>24, y>>>16, y>>>8, y, 1});
}
/** Write the given chunk into the given file; Note: data.length must be at least 4. */
private static void writeChunk (RandomAccessFile file, int[] data) throws IOException {
int crc = (-1), len = data.length - 4;
file.write((len>>>24) & 255); file.write((len>>>16) & 255); file.write((len>>>8) & 255); file.write(len & 255);
for(int i=0; i<data.length; i++) { int x = data[i]; crc = table[(crc ^ x) & 255] ^ (crc >>> 8); file.write(x & 255); }
crc = crc ^ (-1);
file.write((crc>>>24) & 255); file.write((crc>>>16) & 255); file.write((crc>>>8) & 255); file.write(crc & 255);
}
/** This precomputed table makes it faster to calculate CRC; this is based on the suggestion in the PNG specification. */
private static final int[] table = new int[] {
0,1996959894,-301047508,-1727442502,124634137,1886057615,-379345611,-1637575261,249268274
,2044508324,-522852066,-1747789432,162941995,2125561021,-407360249,-1866523247,498536548,1789927666
,-205950648,-2067906082,450548861,1843258603,-187386543,-2083289657,325883990,1684777152,-43845254
,-1973040660,335633487,1661365465,-99664541,-1928851979,997073096,1281953886,-715111964,-1570279054
,1006888145,1258607687,-770865667,-1526024853,901097722,1119000684,-608450090,-1396901568,853044451
,1172266101,-589951537,-1412350631,651767980,1373503546,-925412992,-1076862698,565507253,1454621731
,-809855591,-1195530993,671266974,1594198024,-972236366,-1324619484,795835527,1483230225,-1050600021
,-1234817731,1994146192,31158534,-1731059524,-271249366,1907459465,112637215,-1614814043,-390540237
,2013776290,251722036,-1777751922,-519137256,2137656763,141376813,-1855689577,-429695999,1802195444
,476864866,-2056965928,-228458418,1812370925,453092731,-2113342271,-183516073,1706088902,314042704
,-1950435094,-54949764,1658658271,366619977,-1932296973,-69972891,1303535960,984961486,-1547960204
,-725929758,1256170817,1037604311,-1529756563,-740887301,1131014506,879679996,-1385723834,-631195440
,1141124467,855842277,-1442165665,-586318647,1342533948,654459306,-1106571248,-921952122,1466479909
,544179635,-1184443383,-832445281,1591671054,702138776,-1328506846,-942167884,1504918807,783551873
,-1212326853,-1061524307,-306674912,-1698712650,62317068,1957810842,-355121351,-1647151185,81470997
,1943803523,-480048366,-1805370492,225274430,2053790376,-468791541,-1828061283,167816743,2097651377
,-267414716,-2029476910,503444072,1762050814,-144550051,-2140837941,426522225,1852507879,-19653770
,-1982649376,282753626,1742555852,-105259153,-1900089351,397917763,1622183637,-690576408,-1580100738
,953729732,1340076626,-776247311,-1497606297,1068828381,1219638859,-670225446,-1358292148,906185462
,1090812512,-547295293,-1469587627,829329135,1181335161,-882789492,-1134132454,628085408,1382605366
,-871598187,-1156888829,570562233,1426400815,-977650754,-1296233688,733239954,1555261956,-1026031705
,-1244606671,752459403,1541320221,-1687895376,-328994266,1969922972,40735498,-1677130071,-351390145
,1913087877,83908371,-1782625662,-491226604,2075208622,213261112,-1831694693,-438977011,2094854071
,198958881,-2032938284,-237706686,1759359992,534414190,-2118248755,-155638181,1873836001,414664567
,-2012718362,-15766928,1711684554,285281116,-1889165569,-127750551,1634467795,376229701,-1609899400
,-686959890,1308918612,956543938,-1486412191,-799009033,1231636301,1047427035,-1362007478,-640263460
,1088359270,936918000,-1447252397,-558129467,1202900863,817233897,-1111625188,-893730166,1404277552
,615818150,-1160759803,-841546093,1423857449,601450431,-1285129682,-1000256840,1567103746,711928724
,-1274298825,-1022587231,1510334235,755167117
};
}