├── .gitignore ├── LICENSE.md ├── README.md ├── build.xml ├── pom.xml └── src ├── net └── sf │ └── image4j │ ├── codec │ ├── bmp │ │ ├── BMPConstants.java │ │ ├── BMPDecoder.java │ │ ├── BMPEncoder.java │ │ ├── BMPImage.java │ │ ├── ColorEntry.java │ │ ├── InfoHeader.java │ │ └── package.html │ ├── ico │ │ ├── ICOConstants.java │ │ ├── ICODecoder.java │ │ ├── ICOEncoder.java │ │ ├── ICOImage.java │ │ ├── IconEntry.java │ │ └── package.html │ └── package.html │ ├── example │ ├── Test.java │ └── package.html │ ├── io │ ├── CountingDataInput.java │ ├── CountingDataInputStream.java │ ├── CountingInput.java │ ├── CountingInputStream.java │ ├── EndianUtils.java │ ├── IOUtils.java │ ├── LittleEndianInputStream.java │ ├── LittleEndianOutputStream.java │ ├── LittleEndianRandomAccessFile.java │ └── package.html │ ├── test │ ├── Test.java │ └── package.html │ └── util │ ├── ConvertUtil.java │ ├── ImageUtil.java │ └── package.html └── overview.html /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | 14 | GNU LESSER GENERAL PUBLIC LICENSE 15 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 16 | 17 | 0. This License Agreement applies to any software library or other 18 | program which contains a notice placed by the copyright holder or 19 | other authorized party saying it may be distributed under the terms of 20 | this Lesser General Public License (also called "this License"). 21 | Each licensee is addressed as "you". 22 | 23 | A "library" means a collection of software functions and/or data 24 | prepared so as to be conveniently linked with application programs 25 | (which use some of those functions and data) to form executables. 26 | 27 | The "Library", below, refers to any such software library or work 28 | which has been distributed under these terms. A "work based on the 29 | Library" means either the Library or any derivative work under 30 | copyright law: that is to say, a work containing the Library or a 31 | portion of it, either verbatim or with modifications and/or translated 32 | straightforwardly into another language. (Hereinafter, translation is 33 | included without limitation in the term "modification".) 34 | 35 | "Source code" for a work means the preferred form of the work for 36 | making modifications to it. For a library, complete source code means 37 | all the source code for all modules it contains, plus any associated 38 | interface definition files, plus the scripts used to control compilation 39 | and installation of the library. 40 | 41 | Activities other than copying, distribution and modification are not 42 | covered by this License; they are outside its scope. The act of 43 | running a program using the Library is not restricted, and output from 44 | such a program is covered only if its contents constitute a work based 45 | on the Library (independent of the use of the Library in a tool for 46 | writing it). Whether that is true depends on what the Library does 47 | and what the program that uses the Library does. 48 | 49 | 1. You may copy and distribute verbatim copies of the Library's 50 | complete source code as you receive it, in any medium, provided that 51 | you conspicuously and appropriately publish on each copy an 52 | appropriate copyright notice and disclaimer of warranty; keep intact 53 | all the notices that refer to this License and to the absence of any 54 | warranty; and distribute a copy of this License along with the 55 | Library. 56 | 57 | You may charge a fee for the physical act of transferring a copy, 58 | and you may at your option offer warranty protection in exchange for a 59 | fee. 60 | 61 | 2. You may modify your copy or copies of the Library or any portion 62 | of it, thus forming a work based on the Library, and copy and 63 | distribute such modifications or work under the terms of Section 1 64 | above, provided that you also meet all of these conditions: 65 | 66 | a) The modified work must itself be a software library. 67 | 68 | b) You must cause the files modified to carry prominent notices 69 | stating that you changed the files and the date of any change. 70 | 71 | c) You must cause the whole of the work to be licensed at no 72 | charge to all third parties under the terms of this License. 73 | 74 | d) If a facility in the modified Library refers to a function or a 75 | table of data to be supplied by an application program that uses 76 | the facility, other than as an argument passed when the facility 77 | is invoked, then you must make a good faith effort to ensure that, 78 | in the event an application does not supply such function or 79 | table, the facility still operates, and performs whatever part of 80 | its purpose remains meaningful. 81 | 82 | (For example, a function in a library to compute square roots has 83 | a purpose that is entirely well-defined independent of the 84 | application. Therefore, Subsection 2d requires that any 85 | application-supplied function or table used by this function must 86 | be optional: if the application does not supply it, the square 87 | root function must still compute square roots.) 88 | 89 | These requirements apply to the modified work as a whole. If 90 | identifiable sections of that work are not derived from the Library, 91 | and can be reasonably considered independent and separate works in 92 | themselves, then this License, and its terms, do not apply to those 93 | sections when you distribute them as separate works. But when you 94 | distribute the same sections as part of a whole which is a work based 95 | on the Library, the distribution of the whole must be on the terms of 96 | this License, whose permissions for other licensees extend to the 97 | entire whole, and thus to each and every part regardless of who wrote 98 | it. 99 | 100 | Thus, it is not the intent of this section to claim rights or contest 101 | your rights to work written entirely by you; rather, the intent is to 102 | exercise the right to control the distribution of derivative or 103 | collective works based on the Library. 104 | 105 | In addition, mere aggregation of another work not based on the Library 106 | with the Library (or with a work based on the Library) on a volume of 107 | a storage or distribution medium does not bring the other work under 108 | the scope of this License. 109 | 110 | 3. You may opt to apply the terms of the ordinary GNU General Public 111 | License instead of this License to a given copy of the Library. To do 112 | this, you must alter all the notices that refer to this License, so 113 | that they refer to the ordinary GNU General Public License, version 2, 114 | instead of to this License. (If a newer version than version 2 of the 115 | ordinary GNU General Public License has appeared, then you can specify 116 | that version instead if you wish.) Do not make any other change in 117 | these notices. 118 | 119 | Once this change is made in a given copy, it is irreversible for 120 | that copy, so the ordinary GNU General Public License applies to all 121 | subsequent copies and derivative works made from that copy. 122 | 123 | This option is useful when you wish to copy part of the code of 124 | the Library into a program that is not a library. 125 | 126 | 4. You may copy and distribute the Library (or a portion or 127 | derivative of it, under Section 2) in object code or executable form 128 | under the terms of Sections 1 and 2 above provided that you accompany 129 | it with the complete corresponding machine-readable source code, which 130 | must be distributed under the terms of Sections 1 and 2 above on a 131 | medium customarily used for software interchange. 132 | 133 | If distribution of object code is made by offering access to copy 134 | from a designated place, then offering equivalent access to copy the 135 | source code from the same place satisfies the requirement to 136 | distribute the source code, even though third parties are not 137 | compelled to copy the source along with the object code. 138 | 139 | 5. A program that contains no derivative of any portion of the 140 | Library, but is designed to work with the Library by being compiled or 141 | linked with it, is called a "work that uses the Library". Such a 142 | work, in isolation, is not a derivative work of the Library, and 143 | therefore falls outside the scope of this License. 144 | 145 | However, linking a "work that uses the Library" with the Library 146 | creates an executable that is a derivative of the Library (because it 147 | contains portions of the Library), rather than a "work that uses the 148 | library". The executable is therefore covered by this License. 149 | Section 6 states terms for distribution of such executables. 150 | 151 | When a "work that uses the Library" uses material from a header file 152 | that is part of the Library, the object code for the work may be a 153 | derivative work of the Library even though the source code is not. 154 | Whether this is true is especially significant if the work can be 155 | linked without the Library, or if the work is itself a library. The 156 | threshold for this to be true is not precisely defined by law. 157 | 158 | If such an object file uses only numerical parameters, data 159 | structure layouts and accessors, and small macros and small inline 160 | functions (ten lines or less in length), then the use of the object 161 | file is unrestricted, regardless of whether it is legally a derivative 162 | work. (Executables containing this object code plus portions of the 163 | Library will still fall under Section 6.) 164 | 165 | Otherwise, if the work is a derivative of the Library, you may 166 | distribute the object code for the work under the terms of Section 6. 167 | Any executables containing that work also fall under Section 6, 168 | whether or not they are linked directly with the Library itself. 169 | 170 | 6. As an exception to the Sections above, you may also combine or 171 | link a "work that uses the Library" with the Library to produce a 172 | work containing portions of the Library, and distribute that work 173 | under terms of your choice, provided that the terms permit 174 | modification of the work for the customer's own use and reverse 175 | engineering for debugging such modifications. 176 | 177 | You must give prominent notice with each copy of the work that the 178 | Library is used in it and that the Library and its use are covered by 179 | this License. You must supply a copy of this License. If the work 180 | during execution displays copyright notices, you must include the 181 | copyright notice for the Library among them, as well as a reference 182 | directing the user to the copy of this License. Also, you must do one 183 | of these things: 184 | 185 | a) Accompany the work with the complete corresponding 186 | machine-readable source code for the Library including whatever 187 | changes were used in the work (which must be distributed under 188 | Sections 1 and 2 above); and, if the work is an executable linked 189 | with the Library, with the complete machine-readable "work that 190 | uses the Library", as object code and/or source code, so that the 191 | user can modify the Library and then relink to produce a modified 192 | executable containing the modified Library. (It is understood 193 | that the user who changes the contents of definitions files in the 194 | Library will not necessarily be able to recompile the application 195 | to use the modified definitions.) 196 | 197 | b) Use a suitable shared library mechanism for linking with the 198 | Library. A suitable mechanism is one that (1) uses at run time a 199 | copy of the library already present on the user's computer system, 200 | rather than copying library functions into the executable, and (2) 201 | will operate properly with a modified version of the library, if 202 | the user installs one, as long as the modified version is 203 | interface-compatible with the version that the work was made with. 204 | 205 | c) Accompany the work with a written offer, valid for at 206 | least three years, to give the same user the materials 207 | specified in Subsection 6a, above, for a charge no more 208 | than the cost of performing this distribution. 209 | 210 | d) If distribution of the work is made by offering access to copy 211 | from a designated place, offer equivalent access to copy the above 212 | specified materials from the same place. 213 | 214 | e) Verify that the user has already received a copy of these 215 | materials or that you have already sent this user a copy. 216 | 217 | For an executable, the required form of the "work that uses the 218 | Library" must include any data and utility programs needed for 219 | reproducing the executable from it. However, as a special exception, 220 | the materials to be distributed need not include anything that is 221 | normally distributed (in either source or binary form) with the major 222 | components (compiler, kernel, and so on) of the operating system on 223 | which the executable runs, unless that component itself accompanies 224 | the executable. 225 | 226 | It may happen that this requirement contradicts the license 227 | restrictions of other proprietary libraries that do not normally 228 | accompany the operating system. Such a contradiction means you cannot 229 | use both them and the Library together in an executable that you 230 | distribute. 231 | 232 | 7. You may place library facilities that are a work based on the 233 | Library side-by-side in a single library together with other library 234 | facilities not covered by this License, and distribute such a combined 235 | library, provided that the separate distribution of the work based on 236 | the Library and of the other library facilities is otherwise 237 | permitted, and provided that you do these two things: 238 | 239 | a) Accompany the combined library with a copy of the same work 240 | based on the Library, uncombined with any other library 241 | facilities. This must be distributed under the terms of the 242 | Sections above. 243 | 244 | b) Give prominent notice with the combined library of the fact 245 | that part of it is a work based on the Library, and explaining 246 | where to find the accompanying uncombined form of the same work. 247 | 248 | 8. You may not copy, modify, sublicense, link with, or distribute 249 | the Library except as expressly provided under this License. Any 250 | attempt otherwise to copy, modify, sublicense, link with, or 251 | distribute the Library is void, and will automatically terminate your 252 | rights under this License. However, parties who have received copies, 253 | or rights, from you under this License will not have their licenses 254 | terminated so long as such parties remain in full compliance. 255 | 256 | 9. You are not required to accept this License, since you have not 257 | signed it. However, nothing else grants you permission to modify or 258 | distribute the Library or its derivative works. These actions are 259 | prohibited by law if you do not accept this License. Therefore, by 260 | modifying or distributing the Library (or any work based on the 261 | Library), you indicate your acceptance of this License to do so, and 262 | all its terms and conditions for copying, distributing or modifying 263 | the Library or works based on it. 264 | 265 | 10. Each time you redistribute the Library (or any work based on the 266 | Library), the recipient automatically receives a license from the 267 | original licensor to copy, distribute, link with or modify the Library 268 | subject to these terms and conditions. You may not impose any further 269 | restrictions on the recipients' exercise of the rights granted herein. 270 | You are not responsible for enforcing compliance by third parties with 271 | this License. 272 | 273 | 11. If, as a consequence of a court judgment or allegation of patent 274 | infringement or for any other reason (not limited to patent issues), 275 | conditions are imposed on you (whether by court order, agreement or 276 | otherwise) that contradict the conditions of this License, they do not 277 | excuse you from the conditions of this License. If you cannot 278 | distribute so as to satisfy simultaneously your obligations under this 279 | License and any other pertinent obligations, then as a consequence you 280 | may not distribute the Library at all. For example, if a patent 281 | license would not permit royalty-free redistribution of the Library by 282 | all those who receive copies directly or indirectly through you, then 283 | the only way you could satisfy both it and this License would be to 284 | refrain entirely from distribution of the Library. 285 | 286 | If any portion of this section is held invalid or unenforceable under any 287 | particular circumstance, the balance of the section is intended to apply, 288 | and the section as a whole is intended to apply in other circumstances. 289 | 290 | It is not the purpose of this section to induce you to infringe any 291 | patents or other property right claims or to contest validity of any 292 | such claims; this section has the sole purpose of protecting the 293 | integrity of the free software distribution system which is 294 | implemented by public license practices. Many people have made 295 | generous contributions to the wide range of software distributed 296 | through that system in reliance on consistent application of that 297 | system; it is up to the author/donor to decide if he or she is willing 298 | to distribute software through any other system and a licensee cannot 299 | impose that choice. 300 | 301 | This section is intended to make thoroughly clear what is believed to 302 | be a consequence of the rest of this License. 303 | 304 | 12. If the distribution and/or use of the Library is restricted in 305 | certain countries either by patents or by copyrighted interfaces, the 306 | original copyright holder who places the Library under this License may add 307 | an explicit geographical distribution limitation excluding those countries, 308 | so that distribution is permitted only in or among countries not thus 309 | excluded. In such case, this License incorporates the limitation as if 310 | written in the body of this License. 311 | 312 | 13. The Free Software Foundation may publish revised and/or new 313 | versions of the Lesser General Public License from time to time. 314 | Such new versions will be similar in spirit to the present version, 315 | but may differ in detail to address new problems or concerns. 316 | 317 | Each version is given a distinguishing version number. If the Library 318 | specifies a version number of this License which applies to it and 319 | "any later version", you have the option of following the terms and 320 | conditions either of that version or of any later version published by 321 | the Free Software Foundation. If the Library does not specify a 322 | license version number, you may choose any version ever published by 323 | the Free Software Foundation. 324 | 325 | 14. If you wish to incorporate parts of the Library into other free 326 | programs whose distribution conditions are incompatible with these, 327 | write to the author to ask for permission. For software which is 328 | copyrighted by the Free Software Foundation, write to the Free 329 | Software Foundation; we sometimes make exceptions for this. Our 330 | decision will be guided by the two goals of preserving the free status 331 | of all derivatives of our free software and of promoting the sharing 332 | and reuse of software generally. 333 | 334 | NO WARRANTY 335 | 336 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 337 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 338 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 339 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 340 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 341 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 342 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 343 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 344 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 345 | 346 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 347 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 348 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 349 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 350 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 351 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 352 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 353 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 354 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 355 | DAMAGES. 356 | 357 | END OF TERMS AND CONDITIONS 358 | 359 | 360 | 361 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # image4j 2 | 3 | ## Overview 4 | 5 | The image4j library allows you to read and write certain image formats in 100% pure Java. 6 | 7 | Currently the following formats are supported: 8 | 9 | * BMP (Microsoft bitmap format - uncompressed; 1, 4, 8, 24 and 32 bit) 10 | * ICO (Microsoft icon format - 1, 4, 8, 24 and 32 bit [XP uncompressed, Vista compressed]) 11 | 12 | ## Purpose 13 | 14 | This project aims to provide: 15 | 16 | * an open source library for handling various image formats in Java 17 | * with a commercial-friendly license 18 | * using only Java code, ie. without using any JNI hacks 19 | * with no dependencies on third-party libraries (where possible) 20 | 21 | ## License 22 | 23 | The image4j library is licensed under the GNU LGPL v2.1 so you are free to use it in your Free Software and Open Source projects, as well as commercial projects, under the terms of the LGPL v2.1. 24 | 25 | ## History 26 | 27 | This project began after I spent hours searching for a library that would meet the above criteria and found that there is probably no such thing - or at least, not any more. The only support I could find for the ICO format was read-only, so doing cool stuff - like generating favicons on the fly - using only Java code was not possible. 28 | 29 | ## QuickStart 30 | 31 | It is possible to decode/encode BMP and ICO files with just one line of code! 32 | 33 | The image4j library consists of only Java code and has no dependencies on third-party libraries, so you just add it to the classpath and you're good to go. 34 | 35 | Although the code is compatible with Java 1.5.0 or later, it should be relatively simple to port the code to an older version, eg. 1.4.2. 36 | 37 | ### BMP 38 | 39 | #### Decode 40 | 41 | 42 | ```java 43 | BufferedImage image = BMPDecoder.read(new File("input.bmp")); 44 | ``` 45 | 46 | #### Encode 47 | 48 | ```java 49 | BMPEncoder.write(image, new File("output.bmp")); 50 | ``` 51 | 52 | ### ICO 53 | 54 | #### Decode 55 | 56 | ```java 57 | List image = ICODecoder.read(new File("input.ico")); 58 | ``` 59 | 60 | #### Encode 61 | 62 | ```java 63 | ICOEncoder.write(images, new File("output.ico")); 64 | ``` 65 | 66 | ## Documentation 67 | 68 | Browse the API Javadocs online. 69 | 70 | ## Download 71 | 72 | Download the latest version at [SourceForge.net](https://sourceforge.net/projects/image4j/) or from the Github [release page](https://github.com/imcdonagh/image4j/releases). 73 | 74 | ## Credits 75 | 76 | * The [File Formats page at DaubNET](https://www.daubnet.com/en/file-formats) for information on various image formats. 77 | * GIMP, which I use for editing images 78 | 79 | ## Disclaimer 80 | 81 | To my knowledge, there are no patents on either the BMP or ICO formats. 82 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 4.0.0 6 | 7 | net.sf.image4j 8 | image4j 9 | 0.7.2 10 | jar 11 | 12 | image4J 13 | The image4j library allows you to read and write certain image formats in 100% pure Java. 14 | 2006 15 | 16 | 17 | 6 18 | 6 19 | 20 | 21 | 22 | ${basedir}/src 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/net/sf/image4j/codec/bmp/BMPConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * BMPConstants.java 3 | * 4 | * Created on 10 May 2006, 08:17 5 | * 6 | * To change this template, choose Tools | Template Manager 7 | * and open the template in the editor. 8 | */ 9 | 10 | package net.sf.image4j.codec.bmp; 11 | 12 | /** 13 | * Provides constants used with BMP format. 14 | * 15 | * @author Ian McDonagh 16 | */ 17 | public class BMPConstants { 18 | 19 | private BMPConstants() { 20 | } 21 | 22 | /** 23 | * The signature for the BMP format header "BM". 24 | */ 25 | public static final String FILE_HEADER = "BM"; 26 | 27 | /** 28 | * Specifies no compression. 29 | * 30 | * @see InfoHeader#iCompression InfoHeader 31 | */ 32 | public static final int BI_RGB = 0; // no compression 33 | 34 | /** 35 | * Specifies 8-bit RLE compression. 36 | * 37 | * @see InfoHeader#iCompression InfoHeader 38 | */ 39 | public static final int BI_RLE8 = 1; // 8bit RLE compression 40 | 41 | /** 42 | * Specifies 4-bit RLE compression. 43 | * 44 | * @see InfoHeader#iCompression InfoHeader 45 | */ 46 | public static final int BI_RLE4 = 2; // 4bit RLE compression 47 | 48 | /** 49 | * Specifies 16-bit or 32-bit "bit field" compression. 50 | * 51 | * @see InfoHeader#iCompression InfoHeader 52 | */ 53 | public static final int BI_BITFIELDS = 3; // 16bit or 32bit "bit field" 54 | // compression. 55 | 56 | /** 57 | * Specifies JPEG compression. 58 | * 59 | * @see InfoHeader#iCompression InfoHeader 60 | */ 61 | public static final int BI_JPEG = 4; // _JPEG compression 62 | 63 | /** 64 | * Specifies PNG compression. 65 | * 66 | * @see InfoHeader#iCompression InfoHeader 67 | */ 68 | public static final int BI_PNG = 5; // PNG compression 69 | } 70 | -------------------------------------------------------------------------------- /src/net/sf/image4j/codec/bmp/BMPDecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Decodes a BMP image from an InputStream to a BufferedImage 3 | * 4 | * @author Ian McDonagh 5 | */ 6 | 7 | package net.sf.image4j.codec.bmp; 8 | 9 | import java.awt.image.*; 10 | import java.io.*; 11 | 12 | import net.sf.image4j.io.*; 13 | 14 | /** 15 | * Decodes images in BMP format. 16 | * @author Ian McDonagh 17 | */ 18 | public class BMPDecoder { 19 | 20 | private BufferedImage img; 21 | private InfoHeader infoHeader; 22 | 23 | /** Creates a new instance of BMPDecoder and reads the BMP data from the source. 24 | * @param in the source InputStream from which to read the BMP data 25 | * @throws java.io.IOException if an error occurs 26 | */ 27 | public BMPDecoder(java.io.InputStream in) throws IOException { 28 | LittleEndianInputStream lis = new LittleEndianInputStream(new CountingInputStream(in)); 29 | 30 | /* header [14] */ 31 | 32 | //signature "BM" [2] 33 | byte[] bsignature = new byte[2]; 34 | lis.read(bsignature); 35 | String signature = new String(bsignature, "UTF-8"); 36 | 37 | if (!signature.equals("BM")) { 38 | throw new IOException("Invalid signature '"+signature+"' for BMP format"); 39 | } 40 | 41 | //file size [4] 42 | int fileSize = lis.readIntLE(); 43 | 44 | //reserved = 0 [4] 45 | int reserved = lis.readIntLE(); 46 | 47 | //DataOffset [4] file offset to raster data 48 | int dataOffset = lis.readIntLE(); 49 | 50 | /* info header [40] */ 51 | 52 | infoHeader = readInfoHeader(lis); 53 | 54 | /* Color table and Raster data */ 55 | 56 | img = read(infoHeader, lis); 57 | } 58 | 59 | /** 60 | * Retrieves a bit from the lowest order byte of the given integer. 61 | * @param bits the source integer, treated as an unsigned byte 62 | * @param index the index of the bit to retrieve, which must be in the range 0..7. 63 | * @return the bit at the specified index, which will be either 0 or 1. 64 | */ 65 | private static int getBit(int bits, int index) { 66 | return (bits >> (7 - index)) & 1; 67 | } 68 | 69 | /** 70 | * Retrieves a nibble (4 bits) from the lowest order byte of the given integer. 71 | * @param nibbles the source integer, treated as an unsigned byte 72 | * @param index the index of the nibble to retrieve, which must be in the range 0..1. 73 | * @return the nibble at the specified index, as an unsigned byte. 74 | */ 75 | private static int getNibble(int nibbles, int index) { 76 | return (nibbles >> (4 * (1 - index))) & 0xF; 77 | } 78 | 79 | /** 80 | * The InfoHeader structure, which provides information about the BMP data. 81 | * @return the InfoHeader structure that was read from the source data when this BMPDecoder 82 | * was created. 83 | */ 84 | public InfoHeader getInfoHeader() { 85 | return infoHeader; 86 | } 87 | 88 | /** 89 | * The decoded image read from the source input. 90 | * @return the BufferedImage representing the BMP image. 91 | */ 92 | public BufferedImage getBufferedImage() { 93 | return img; 94 | } 95 | 96 | private static void getColorTable(ColorEntry[] colorTable, byte[] ar, byte[] ag, byte[] ab) { 97 | for (int i = 0; i < colorTable.length; i++) { 98 | ar[i] = (byte) colorTable[i].bRed; 99 | ag[i] = (byte) colorTable[i].bGreen; 100 | ab[i] = (byte) colorTable[i].bBlue; 101 | } 102 | } 103 | 104 | /** 105 | * Reads the BMP info header structure from the given InputStream. 106 | * @param lis the InputStream to read 107 | * @return the InfoHeader structure 108 | * @throws java.io.IOException if an error occurred 109 | */ 110 | public static InfoHeader readInfoHeader(net.sf.image4j.io.LittleEndianInputStream lis) throws IOException { 111 | InfoHeader infoHeader = new InfoHeader(lis); 112 | return infoHeader; 113 | } 114 | 115 | /** 116 | * @since 0.6 117 | */ 118 | public static InfoHeader readInfoHeader(net.sf.image4j.io.LittleEndianInputStream lis, int infoSize) throws IOException { 119 | InfoHeader infoHeader = new InfoHeader(lis, infoSize); 120 | return infoHeader; 121 | } 122 | 123 | /** 124 | * Reads the BMP data from the given InputStream using the information 125 | * contained in the InfoHeader. 126 | * @param lis the source input 127 | * @param infoHeader an InfoHeader that was read by a call to 128 | * {@link #readInfoHeader(net.sf.image4j.io.LittleEndianInputStream) readInfoHeader()}. 129 | * @return the decoded image read from the source input 130 | * @throws java.io.IOException if an error occurs 131 | */ 132 | public static BufferedImage read(InfoHeader infoHeader, net.sf.image4j.io.LittleEndianInputStream lis) throws IOException { 133 | BufferedImage img = null; 134 | 135 | /* Color table (palette) */ 136 | 137 | ColorEntry[] colorTable = null; 138 | 139 | //color table is only present for 1, 4 or 8 bit (indexed) images 140 | if (infoHeader.sBitCount <= 8) { 141 | colorTable = readColorTable(infoHeader, lis); 142 | } 143 | 144 | img = read(infoHeader, lis, colorTable); 145 | 146 | return img; 147 | } 148 | 149 | /** 150 | * Reads the BMP data from the given InputStream using the information 151 | * contained in the InfoHeader. 152 | * @param colorTable ColorEntry array containing palette 153 | * @param infoHeader an InfoHeader that was read by a call to 154 | * {@link #readInfoHeader(net.sf.image4j.io.LittleEndianInputStream) readInfoHeader()}. 155 | * @param lis the source input 156 | * @return the decoded image read from the source input 157 | * @throws java.io.IOException if any error occurs 158 | */ 159 | public static BufferedImage read(InfoHeader infoHeader, net.sf.image4j.io.LittleEndianInputStream lis, 160 | ColorEntry[] colorTable) throws IOException { 161 | 162 | BufferedImage img = null; 163 | 164 | //1-bit (monochrome) uncompressed 165 | if (infoHeader.sBitCount == 1 && infoHeader.iCompression == BMPConstants.BI_RGB) { 166 | 167 | img = read1(infoHeader, lis, colorTable); 168 | 169 | } 170 | //4-bit uncompressed 171 | else if (infoHeader.sBitCount == 4 && infoHeader.iCompression == BMPConstants.BI_RGB) { 172 | 173 | img = read4(infoHeader, lis, colorTable); 174 | 175 | } 176 | //8-bit uncompressed 177 | else if (infoHeader.sBitCount == 8 && infoHeader.iCompression == BMPConstants.BI_RGB) { 178 | 179 | img = read8(infoHeader, lis, colorTable); 180 | 181 | } 182 | //24-bit uncompressed 183 | else if (infoHeader.sBitCount == 24 && infoHeader.iCompression == BMPConstants.BI_RGB) { 184 | 185 | img = read24(infoHeader, lis); 186 | 187 | } 188 | //32bit uncompressed 189 | else if (infoHeader.sBitCount == 32 && infoHeader.iCompression == BMPConstants.BI_RGB) { 190 | 191 | img = read32(infoHeader, lis); 192 | 193 | } else { 194 | throw new IOException("Unrecognized bitmap format: bit count="+infoHeader.sBitCount+", compression="+ 195 | infoHeader.iCompression); 196 | } 197 | 198 | return img; 199 | } 200 | 201 | /** 202 | * Reads the ColorEntry table from the given InputStream using 203 | * the information contained in the given infoHeader. 204 | * @param infoHeader the InfoHeader structure, which was read using 205 | * {@link #readInfoHeader(net.sf.image4j.io.LittleEndianInputStream) readInfoHeader()} 206 | * @param lis the InputStream to read 207 | * @throws java.io.IOException if an error occurs 208 | * @return the decoded image read from the source input 209 | */ 210 | public static ColorEntry[] readColorTable(InfoHeader infoHeader, net.sf.image4j.io.LittleEndianInputStream lis) throws IOException { 211 | ColorEntry[] colorTable = new ColorEntry[infoHeader.iNumColors]; 212 | for (int i = 0; i < infoHeader.iNumColors; i++) { 213 | ColorEntry ce = new ColorEntry(lis); 214 | colorTable[i] = ce; 215 | } 216 | return colorTable; 217 | } 218 | 219 | /** 220 | * Reads 1-bit uncompressed bitmap raster data, which may be monochrome depending on the 221 | * palette entries in colorTable. 222 | * @param infoHeader the InfoHeader structure, which was read using 223 | * {@link #readInfoHeader(net.sf.image4j.io.LittleEndianInputStream) readInfoHeader()} 224 | * @param lis the source input 225 | * @param colorTable ColorEntry array specifying the palette, which 226 | * must not be null. 227 | * @throws java.io.IOException if an error occurs 228 | * @return the decoded image read from the source input 229 | */ 230 | public static BufferedImage read1(InfoHeader infoHeader, 231 | net.sf.image4j.io.LittleEndianInputStream lis, 232 | ColorEntry[] colorTable) throws IOException { 233 | //1 bit per pixel or 8 pixels per byte 234 | //each pixel specifies the palette index 235 | 236 | byte[] ar = new byte[colorTable.length]; 237 | byte[] ag = new byte[colorTable.length]; 238 | byte[] ab = new byte[colorTable.length]; 239 | 240 | getColorTable(colorTable, ar, ag, ab); 241 | 242 | IndexColorModel icm = new IndexColorModel( 243 | 1, 2, ar, ag, ab 244 | ); 245 | 246 | // Create indexed image 247 | BufferedImage img = new BufferedImage( 248 | infoHeader.iWidth, infoHeader.iHeight, 249 | BufferedImage.TYPE_BYTE_BINARY, 250 | icm 251 | ); 252 | // We'll use the raster to set samples instead of RGB values. 253 | // The SampleModel of an indexed image interprets samples as 254 | // the index of the colour for a pixel, which is perfect for use here. 255 | WritableRaster raster = img.getRaster(); 256 | 257 | //padding 258 | 259 | int dataBitsPerLine = infoHeader.iWidth; 260 | int bitsPerLine = dataBitsPerLine; 261 | if (bitsPerLine % 32 != 0) { 262 | bitsPerLine = (bitsPerLine / 32 + 1) * 32; 263 | } 264 | int padBits = bitsPerLine - dataBitsPerLine; 265 | int padBytes = padBits / 8; 266 | 267 | int bytesPerLine = (int) (bitsPerLine / 8); 268 | int[] line = new int[bytesPerLine]; 269 | 270 | for (int y = infoHeader.iHeight - 1; y >= 0; y--) { 271 | for (int i = 0; i < bytesPerLine; i++) { 272 | line[i] = lis.readUnsignedByte(); 273 | } 274 | 275 | for (int x = 0; x < infoHeader.iWidth; x++) { 276 | int i = x / 8; 277 | int v = line[i]; 278 | int b = x % 8; 279 | int index = getBit(v, b); 280 | //int rgb = c[index]; 281 | //img.setRGB(x, y, rgb); 282 | //set the sample (colour index) for the pixel 283 | raster.setSample(x, y, 0, index); 284 | } 285 | } 286 | 287 | return img; 288 | } 289 | 290 | /** 291 | * Reads 4-bit uncompressed bitmap raster data, which is interpreted based on the colours 292 | * specified in the palette. 293 | * @param infoHeader the InfoHeader structure, which was read using 294 | * {@link #readInfoHeader(net.sf.image4j.io.LittleEndianInputStream) readInfoHeader()} 295 | * @param lis the source input 296 | * @param colorTable ColorEntry array specifying the palette, which 297 | * must not be null. 298 | * @throws java.io.IOException if an error occurs 299 | * @return the decoded image read from the source input 300 | */ 301 | public static BufferedImage read4(InfoHeader infoHeader, 302 | net.sf.image4j.io.LittleEndianInputStream lis, 303 | ColorEntry[] colorTable) throws IOException { 304 | 305 | // 2 pixels per byte or 4 bits per pixel. 306 | // Colour for each pixel specified by the color index in the pallette. 307 | 308 | byte[] ar = new byte[colorTable.length]; 309 | byte[] ag = new byte[colorTable.length]; 310 | byte[] ab = new byte[colorTable.length]; 311 | 312 | getColorTable(colorTable, ar, ag, ab); 313 | 314 | IndexColorModel icm = new IndexColorModel( 315 | 4, infoHeader.iNumColors, ar, ag, ab 316 | ); 317 | 318 | BufferedImage img = new BufferedImage( 319 | infoHeader.iWidth, infoHeader.iHeight, 320 | BufferedImage.TYPE_BYTE_BINARY, 321 | icm 322 | ); 323 | 324 | WritableRaster raster = img.getRaster(); 325 | 326 | //padding 327 | int bitsPerLine = infoHeader.iWidth * 4; 328 | if (bitsPerLine % 32 != 0) { 329 | bitsPerLine = (bitsPerLine / 32 + 1) * 32; 330 | } 331 | int bytesPerLine = (int) (bitsPerLine / 8); 332 | 333 | int[] line = new int[bytesPerLine]; 334 | 335 | for (int y = infoHeader.iHeight - 1; y >= 0; y--) { 336 | //scan line 337 | for (int i = 0; i < bytesPerLine; i++) { 338 | int b = lis.readUnsignedByte(); 339 | line[i] = b; 340 | } 341 | 342 | //get pixels 343 | for (int x = 0; x < infoHeader.iWidth; x++) { 344 | //get byte index for line 345 | int b = x / 2; // 2 pixels per byte 346 | int i = x % 2; 347 | int n = line[b]; 348 | int index = getNibble(n, i); 349 | raster.setSample(x, y, 0, index); 350 | } 351 | } 352 | 353 | return img; 354 | } 355 | 356 | /** 357 | * Reads 8-bit uncompressed bitmap raster data, which is interpreted based on the colours 358 | * specified in the palette. 359 | * @param infoHeader the InfoHeader structure, which was read using 360 | * {@link #readInfoHeader(net.sf.image4j.io.LittleEndianInputStream) readInfoHeader()} 361 | * @param lis the source input 362 | * @param colorTable ColorEntry array specifying the palette, which 363 | * must not be null. 364 | * @throws java.io.IOException if an error occurs 365 | * @return the decoded image read from the source input 366 | */ 367 | public static BufferedImage read8(InfoHeader infoHeader, 368 | net.sf.image4j.io.LittleEndianInputStream lis, 369 | ColorEntry[] colorTable) throws IOException { 370 | //1 byte per pixel 371 | // color index 1 (index of color in palette) 372 | //lines padded to nearest 32bits 373 | //no alpha 374 | 375 | byte[] ar = new byte[colorTable.length]; 376 | byte[] ag = new byte[colorTable.length]; 377 | byte[] ab = new byte[colorTable.length]; 378 | 379 | getColorTable(colorTable, ar, ag, ab); 380 | 381 | IndexColorModel icm = new IndexColorModel( 382 | 8, infoHeader.iNumColors, ar, ag, ab 383 | ); 384 | 385 | BufferedImage img = new BufferedImage( 386 | infoHeader.iWidth, infoHeader.iHeight, 387 | BufferedImage.TYPE_BYTE_INDEXED, 388 | icm 389 | ); 390 | 391 | WritableRaster raster = img.getRaster(); 392 | 393 | /* 394 | //create color pallette 395 | int[] c = new int[infoHeader.iNumColors]; 396 | for (int i = 0; i < c.length; i++) { 397 | int r = colorTable[i].bRed; 398 | int g = colorTable[i].bGreen; 399 | int b = colorTable[i].bBlue; 400 | c[i] = (r << 16) | (g << 8) | (b); 401 | } 402 | */ 403 | 404 | //padding 405 | int dataPerLine = infoHeader.iWidth; 406 | int bytesPerLine = dataPerLine; 407 | if (bytesPerLine % 4 != 0) { 408 | bytesPerLine = (bytesPerLine / 4 + 1) * 4; 409 | } 410 | int padBytesPerLine = bytesPerLine - dataPerLine; 411 | 412 | for (int y = infoHeader.iHeight - 1; y >= 0; y--) { 413 | for (int x = 0; x < infoHeader.iWidth; x++) { 414 | int b = lis.readUnsignedByte(); 415 | //int clr = c[b]; 416 | //img.setRGB(x, y, clr); 417 | //set sample (colour index) for pixel 418 | raster.setSample(x, y , 0, b); 419 | } 420 | 421 | lis.skip(padBytesPerLine); 422 | } 423 | 424 | return img; 425 | } 426 | 427 | /** 428 | * Reads 24-bit uncompressed bitmap raster data. 429 | * @param lis the source input 430 | * @param infoHeader the InfoHeader structure, which was read using 431 | * {@link #readInfoHeader(net.sf.image4j.io.LittleEndianInputStream) readInfoHeader()} 432 | * @throws java.io.IOException if an error occurs 433 | * @return the decoded image read from the source input 434 | */ 435 | public static BufferedImage read24(InfoHeader infoHeader, 436 | net.sf.image4j.io.LittleEndianInputStream lis) throws IOException { 437 | //3 bytes per pixel 438 | // blue 1 439 | // green 1 440 | // red 1 441 | // lines padded to nearest 32 bits 442 | // no alpha 443 | 444 | BufferedImage img = new BufferedImage( 445 | infoHeader.iWidth, infoHeader.iHeight, 446 | BufferedImage.TYPE_INT_RGB 447 | ); 448 | 449 | WritableRaster raster = img.getRaster(); 450 | 451 | //padding to nearest 32 bits 452 | int dataPerLine = infoHeader.iWidth * 3; 453 | int bytesPerLine = dataPerLine; 454 | if (bytesPerLine % 4 != 0) { 455 | bytesPerLine = (bytesPerLine / 4 + 1) * 4; 456 | } 457 | int padBytesPerLine = bytesPerLine - dataPerLine; 458 | 459 | for (int y = infoHeader.iHeight - 1; y >= 0; y--) { 460 | for (int x = 0; x < infoHeader.iWidth; x++) { 461 | int b = lis.readUnsignedByte(); 462 | int g = lis.readUnsignedByte(); 463 | int r = lis.readUnsignedByte(); 464 | 465 | //int c = 0x00000000 | (r << 16) | (g << 8) | (b); 466 | //System.out.println(x + ","+y+"="+Integer.toHexString(c)); 467 | //img.setRGB(x, y, c); 468 | raster.setSample(x, y, 0, r); 469 | raster.setSample(x, y, 1, g); 470 | raster.setSample(x, y, 2, b); 471 | } 472 | lis.skip(padBytesPerLine); 473 | } 474 | 475 | return img; 476 | } 477 | 478 | 479 | /** 480 | * Reads 32-bit uncompressed bitmap raster data, with transparency. 481 | * @param lis the source input 482 | * @param infoHeader the InfoHeader structure, which was read using 483 | * {@link #readInfoHeader(net.sf.image4j.io.LittleEndianInputStream) readInfoHeader()} 484 | * @throws java.io.IOException if an error occurs 485 | * @return the decoded image read from the source input 486 | */ 487 | public static BufferedImage read32(InfoHeader infoHeader, 488 | net.sf.image4j.io.LittleEndianInputStream lis) throws IOException { 489 | //4 bytes per pixel 490 | // blue 1 491 | // green 1 492 | // red 1 493 | // alpha 1 494 | //No padding since each pixel = 32 bits 495 | 496 | BufferedImage img = new BufferedImage( 497 | infoHeader.iWidth, infoHeader.iHeight, 498 | BufferedImage.TYPE_INT_ARGB 499 | ); 500 | 501 | WritableRaster rgb = img.getRaster(); 502 | WritableRaster alpha = img.getAlphaRaster(); 503 | 504 | for (int y = infoHeader.iHeight - 1; y >= 0; y--) { 505 | for (int x = 0; x < infoHeader.iWidth; x++) { 506 | int b = lis.readUnsignedByte(); 507 | int g = lis.readUnsignedByte(); 508 | int r = lis.readUnsignedByte(); 509 | int a = lis.readUnsignedByte(); 510 | rgb.setSample(x, y, 0, r); 511 | rgb.setSample(x, y, 1, g); 512 | rgb.setSample(x, y, 2, b); 513 | alpha.setSample(x, y, 0, a); 514 | } 515 | } 516 | 517 | return img; 518 | } 519 | 520 | /** 521 | * Reads and decodes BMP data from the source file. 522 | * @param file the source file 523 | * @throws java.io.IOException if an error occurs 524 | * @return the decoded image read from the source file 525 | */ 526 | public static BufferedImage read(java.io.File file) throws IOException { 527 | java.io.FileInputStream fin = new java.io.FileInputStream(file); 528 | try { 529 | return read(new BufferedInputStream(fin)); 530 | } finally { 531 | try { 532 | fin.close(); 533 | } catch (IOException ex) { } 534 | } 535 | } 536 | 537 | /** 538 | * Reads and decodes BMP data from the source input. 539 | * @param in the source input 540 | * @throws java.io.IOException if an error occurs 541 | * @return the decoded image read from the source file 542 | */ 543 | public static BufferedImage read(java.io.InputStream in) throws IOException { 544 | BMPDecoder d = new BMPDecoder(in); 545 | return d.getBufferedImage(); 546 | } 547 | 548 | /** 549 | * Reads and decodes BMP data from the source file, together with metadata. 550 | * @param file the source file 551 | * @throws java.io.IOException if an error occurs 552 | * @return the decoded image read from the source file 553 | * @since 0.7 554 | */ 555 | public static BMPImage readExt(java.io.File file) throws IOException { 556 | java.io.FileInputStream fin = new java.io.FileInputStream(file); 557 | try { 558 | return readExt(new BufferedInputStream(fin)); 559 | } finally { 560 | try { 561 | fin.close(); 562 | } catch (IOException ex) { } 563 | } 564 | } 565 | 566 | /** 567 | * Reads and decodes BMP data from the source input, together with metadata. 568 | * @param in the source input 569 | * @throws java.io.IOException if an error occurs 570 | * @return the decoded image read from the source file 571 | * @since 0.7 572 | */ 573 | public static BMPImage readExt(java.io.InputStream in) throws IOException { 574 | BMPDecoder d = new BMPDecoder(in); 575 | BMPImage ret = new BMPImage(d.getBufferedImage(), d.getInfoHeader()); 576 | return ret; 577 | } 578 | } 579 | -------------------------------------------------------------------------------- /src/net/sf/image4j/codec/bmp/BMPEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * BMPEncoder.java 3 | * 4 | * Created on 11 May 2006, 04:19 5 | * 6 | * To change this template, choose Tools | Template Manager 7 | * and open the template in the editor. 8 | */ 9 | 10 | package net.sf.image4j.codec.bmp; 11 | 12 | import java.awt.image.BufferedImage; 13 | import java.awt.image.IndexColorModel; 14 | import java.awt.image.Raster; 15 | import java.io.*; 16 | 17 | import net.sf.image4j.io.LittleEndianOutputStream; 18 | 19 | /** 20 | * Encodes images in BMP format. 21 | * @author Ian McDonagh 22 | */ 23 | public class BMPEncoder { 24 | 25 | /** Creates a new instance of BMPEncoder */ 26 | private BMPEncoder() { 27 | } 28 | 29 | /** 30 | * Encodes and writes BMP data the output file 31 | * @param img the image to encode 32 | * @param file the file to which encoded data will be written 33 | * @throws java.io.IOException if an error occurs 34 | */ 35 | public static void write(BufferedImage img, java.io.File file) throws IOException { 36 | java.io.FileOutputStream fout = new java.io.FileOutputStream(file); 37 | try { 38 | BufferedOutputStream out = new BufferedOutputStream(fout); 39 | write(img, out); 40 | out.flush(); 41 | } finally { 42 | try { 43 | fout.close(); 44 | } catch (IOException ex) { } 45 | } 46 | } 47 | 48 | /** 49 | * Encodes and writes BMP data to the output 50 | * @param img the image to encode 51 | * @param os the output to which encoded data will be written 52 | * @throws java.io.IOException if an error occurs 53 | */ 54 | public static void write(BufferedImage img, java.io.OutputStream os) throws IOException { 55 | 56 | // create info header 57 | 58 | InfoHeader ih = createInfoHeader(img); 59 | 60 | // Create colour map if the image uses an indexed colour model. 61 | // Images with colour depth of 8 bits or less use an indexed colour model. 62 | 63 | int mapSize = 0; 64 | IndexColorModel icm = null; 65 | 66 | if (ih.sBitCount <= 8) { 67 | icm = (IndexColorModel) img.getColorModel(); 68 | mapSize = icm.getMapSize(); 69 | } 70 | 71 | // Calculate header size 72 | 73 | int headerSize = 14 //file header 74 | + ih.iSize //info header 75 | ; 76 | 77 | // Calculate map size 78 | 79 | int mapBytes = 4 * mapSize; 80 | 81 | // Calculate data offset 82 | 83 | int dataOffset = headerSize + mapBytes; 84 | 85 | // Calculate bytes per line 86 | 87 | int bytesPerLine = 0; 88 | 89 | switch (ih.sBitCount) { 90 | case 1: 91 | bytesPerLine = getBytesPerLine1(ih.iWidth); 92 | break; 93 | case 4: 94 | bytesPerLine = getBytesPerLine4(ih.iWidth); 95 | break; 96 | case 8: 97 | bytesPerLine = getBytesPerLine8(ih.iWidth); 98 | break; 99 | case 24: 100 | bytesPerLine = getBytesPerLine24(ih.iWidth); 101 | break; 102 | case 32: 103 | bytesPerLine = ih.iWidth * 4; 104 | break; 105 | } 106 | 107 | // calculate file size 108 | 109 | int fileSize = dataOffset + bytesPerLine * ih.iHeight; 110 | 111 | // output little endian byte order 112 | 113 | LittleEndianOutputStream out = new LittleEndianOutputStream(os); 114 | 115 | //write file header 116 | writeFileHeader(fileSize, dataOffset, out); 117 | 118 | //write info header 119 | ih.write(out); 120 | 121 | //write color map (bit count <= 8) 122 | if (ih.sBitCount <= 8) { 123 | writeColorMap(icm, out); 124 | } 125 | 126 | //write raster data 127 | switch (ih.sBitCount) { 128 | case 1: 129 | write1(img.getRaster(), out); 130 | break; 131 | case 4: 132 | write4(img.getRaster(), out); 133 | break; 134 | case 8: 135 | write8(img.getRaster(), out); 136 | break; 137 | case 24: 138 | write24(img.getRaster(), out); 139 | break; 140 | case 32: 141 | write32(img.getRaster(), img.getAlphaRaster(), out); 142 | break; 143 | } 144 | } 145 | 146 | /** 147 | * Creates an InfoHeader from the source image. 148 | * @param img the source image 149 | * @return the resultant InfoHeader structure 150 | */ 151 | public static InfoHeader createInfoHeader(BufferedImage img) { 152 | InfoHeader ret = new InfoHeader(); 153 | ret.iColorsImportant = 0; 154 | ret.iColorsUsed = 0; 155 | ret.iCompression = 0; 156 | ret.iHeight = img.getHeight(); 157 | ret.iWidth = img.getWidth(); 158 | ret.sBitCount = (short) img.getColorModel().getPixelSize(); 159 | ret.iNumColors = 1 << (ret.sBitCount == 32 ? 24 : ret.sBitCount); 160 | ret.iImageSize = 0; 161 | return ret; 162 | } 163 | 164 | /** 165 | * Writes the file header. 166 | * @param fileSize the calculated file size for the BMP data being written 167 | * @param dataOffset the calculated offset within the BMP data where the actual bitmap begins 168 | * @param out the output to which the file header will be written 169 | * @throws java.io.IOException if an error occurs 170 | */ 171 | public static void writeFileHeader(int fileSize, int dataOffset, 172 | net.sf.image4j.io.LittleEndianOutputStream out) throws IOException { 173 | //signature 174 | byte[] signature = BMPConstants.FILE_HEADER.getBytes("UTF-8"); 175 | out.write(signature); 176 | //file size 177 | out.writeIntLE(fileSize); 178 | //reserved 179 | out.writeIntLE(0); 180 | //data offset 181 | out.writeIntLE(dataOffset); 182 | } 183 | 184 | /** 185 | * Writes the colour map resulting from the source IndexColorModel. 186 | * @param icm the source IndexColorModel 187 | * @param out the output to which the colour map will be written 188 | * @throws java.io.IOException if an error occurs 189 | */ 190 | public static void writeColorMap(IndexColorModel icm, net.sf.image4j.io.LittleEndianOutputStream out) throws IOException { 191 | int mapSize = icm.getMapSize(); 192 | for (int i = 0; i < mapSize; i++) { 193 | int rgb = icm.getRGB(i); 194 | int r = (rgb >> 16) & 0xFF; 195 | int g = (rgb >> 8) & 0xFF; 196 | int b = (rgb) &0xFF; 197 | out.writeByte(b); 198 | out.writeByte(g); 199 | out.writeByte(r); 200 | out.writeByte(0); 201 | } 202 | } 203 | 204 | /** 205 | * Calculates the number of bytes per line required for the given width in pixels, 206 | * for a 1-bit bitmap. Lines are always padded to the next 4-byte boundary. 207 | * @param width the width in pixels 208 | * @return the number of bytes per line 209 | */ 210 | public static int getBytesPerLine1(int width) { 211 | int ret = (int) width / 8; 212 | if (ret * 8 < width) { 213 | ret++; 214 | } 215 | if (ret % 4 != 0) { 216 | ret = (ret / 4 + 1) * 4; 217 | } 218 | return ret; 219 | } 220 | 221 | /** 222 | * Calculates the number of bytes per line required for the given with in pixels, 223 | * for a 4-bit bitmap. Lines are always padded to the next 4-byte boundary. 224 | * @param width the width in pixels 225 | * @return the number of bytes per line 226 | */ 227 | public static int getBytesPerLine4(int width) { 228 | int ret = (int) width / 2; 229 | if (ret % 4 != 0) { 230 | ret = (ret / 4 + 1) * 4; 231 | } 232 | return ret; 233 | } 234 | 235 | /** 236 | * Calculates the number of bytes per line required for the given with in pixels, 237 | * for a 8-bit bitmap. Lines are always padded to the next 4-byte boundary. 238 | * @param width the width in pixels 239 | * @return the number of bytes per line 240 | */ 241 | public static int getBytesPerLine8(int width) { 242 | int ret = width; 243 | if (ret % 4 != 0) { 244 | ret = (ret / 4 + 1) * 4; 245 | } 246 | return ret; 247 | } 248 | 249 | /** 250 | * Calculates the number of bytes per line required for the given with in pixels, 251 | * for a 24-bit bitmap. Lines are always padded to the next 4-byte boundary. 252 | * @param width the width in pixels 253 | * @return the number of bytes per line 254 | */ 255 | public static int getBytesPerLine24(int width) { 256 | int ret = width * 3; 257 | if (ret % 4 != 0) { 258 | ret = (ret / 4 + 1) * 4; 259 | } 260 | return ret; 261 | } 262 | 263 | /** 264 | * Calculates the size in bytes of a bitmap with the specified size and colour depth. 265 | * @param w the width in pixels 266 | * @param h the height in pixels 267 | * @param bpp the colour depth (bits per pixel) 268 | * @return the size of the bitmap in bytes 269 | */ 270 | public static int getBitmapSize(int w, int h, int bpp) { 271 | int bytesPerLine = 0; 272 | switch (bpp) { 273 | case 1: 274 | bytesPerLine = getBytesPerLine1(w); 275 | break; 276 | case 4: 277 | bytesPerLine = getBytesPerLine4(w); 278 | break; 279 | case 8: 280 | bytesPerLine = getBytesPerLine8(w); 281 | break; 282 | case 24: 283 | bytesPerLine = getBytesPerLine24(w); 284 | break; 285 | case 32: 286 | bytesPerLine = w * 4; 287 | break; 288 | } 289 | int ret = bytesPerLine * h; 290 | return ret; 291 | } 292 | 293 | /** 294 | * Encodes and writes raster data as a 1-bit bitmap. 295 | * @param raster the source raster data 296 | * @param out the output to which the bitmap will be written 297 | * @throws java.io.IOException if an error occurs 298 | */ 299 | public static void write1(Raster raster, net.sf.image4j.io.LittleEndianOutputStream out) throws IOException { 300 | int bytesPerLine = getBytesPerLine1(raster.getWidth()); 301 | 302 | byte[] line = new byte[bytesPerLine]; 303 | 304 | for (int y = raster.getHeight() - 1; y >= 0; y--) { 305 | for (int i = 0; i < bytesPerLine; i++) { 306 | line[i] = 0; 307 | } 308 | 309 | for (int x = 0; x < raster.getWidth(); x++) { 310 | int bi = x / 8; 311 | int i = x % 8; 312 | int index = raster.getSample(x, y, 0); 313 | line[bi] = setBit(line[bi], i, index); 314 | } 315 | 316 | out.write(line); 317 | } 318 | } 319 | 320 | /** 321 | * Encodes and writes raster data as a 4-bit bitmap. 322 | * @param raster the source raster data 323 | * @param out the output to which the bitmap will be written 324 | * @throws java.io.IOException if an error occurs 325 | */ 326 | public static void write4(Raster raster, net.sf.image4j.io.LittleEndianOutputStream out) throws IOException { 327 | 328 | // The approach taken here is to use a buffer to hold encoded raster data 329 | // one line at a time. 330 | // Perhaps we could just write directly to output instead 331 | // and avoid using a buffer altogether. Hypothetically speaking, 332 | // a very wide image would require a large line buffer here, but then again, 333 | // large 4 bit bitmaps are pretty uncommon, so using the line buffer approach 334 | // should be okay. 335 | 336 | int width = raster.getWidth(); 337 | int height = raster.getHeight(); 338 | 339 | // calculate bytes per line 340 | int bytesPerLine = getBytesPerLine4(width); 341 | 342 | // line buffer 343 | byte[] line = new byte[bytesPerLine]; 344 | 345 | // encode and write lines 346 | for (int y = height - 1; y >= 0; y--) { 347 | 348 | // clear line buffer 349 | for (int i = 0; i < bytesPerLine; i++) { 350 | line[i] = 0; 351 | } 352 | 353 | // encode raster data for line 354 | for (int x = 0; x < width; x++) { 355 | 356 | // calculate buffer index 357 | int bi = x / 2; 358 | 359 | // calculate nibble index (high order or low order) 360 | int i = x % 2; 361 | 362 | // get color index 363 | int index = raster.getSample(x, y, 0); 364 | // set color index in buffer 365 | line[bi] = setNibble(line[bi], i, index); 366 | } 367 | 368 | // write line data (padding bytes included) 369 | out.write(line); 370 | } 371 | } 372 | 373 | /** 374 | * Encodes and writes raster data as an 8-bit bitmap. 375 | * @param raster the source raster data 376 | * @param out the output to which the bitmap will be written 377 | * @throws java.io.IOException if an error occurs 378 | */ 379 | public static void write8(Raster raster, net.sf.image4j.io.LittleEndianOutputStream out) throws IOException { 380 | 381 | int width = raster.getWidth(); 382 | int height = raster.getHeight(); 383 | 384 | // calculate bytes per line 385 | int bytesPerLine = getBytesPerLine8(width); 386 | 387 | // write lines 388 | for (int y = height - 1; y >= 0; y--) { 389 | 390 | // write raster data for each line 391 | for (int x = 0; x < width; x++) { 392 | 393 | // get color index for pixel 394 | int index = raster.getSample(x, y, 0); 395 | 396 | // write color index 397 | out.writeByte(index); 398 | } 399 | 400 | // write padding bytes at end of line 401 | for (int i = width; i < bytesPerLine; i++) { 402 | out.writeByte(0); 403 | } 404 | 405 | } 406 | } 407 | 408 | /** 409 | * Encodes and writes raster data as a 24-bit bitmap. 410 | * @param raster the source raster data 411 | * @param out the output to which the bitmap will be written 412 | * @throws java.io.IOException if an error occurs 413 | */ 414 | public static void write24(Raster raster, net.sf.image4j.io.LittleEndianOutputStream out) throws IOException { 415 | 416 | int width = raster.getWidth(); 417 | int height = raster.getHeight(); 418 | 419 | // calculate bytes per line 420 | int bytesPerLine = getBytesPerLine24(width); 421 | 422 | // write lines 423 | for (int y = height - 1; y >= 0; y--) { 424 | 425 | // write pixel data for each line 426 | for (int x = 0; x < width; x++) { 427 | 428 | // get RGB values for pixel 429 | int r = raster.getSample(x, y, 0); 430 | int g = raster.getSample(x, y, 1); 431 | int b = raster.getSample(x, y, 2); 432 | 433 | // write RGB values 434 | out.writeByte(b); 435 | out.writeByte(g); 436 | out.writeByte(r); 437 | } 438 | 439 | // write padding bytes at end of line 440 | for (int i = width * 3; i < bytesPerLine; i++) { 441 | out.writeByte(0); 442 | } 443 | } 444 | } 445 | 446 | /** 447 | * Encodes and writes raster data, together with alpha (transparency) data, as a 32-bit bitmap. 448 | * @param raster the source raster data 449 | * @param alpha the source alpha data 450 | * @param out the output to which the bitmap will be written 451 | * @throws java.io.IOException if an error occurs 452 | */ 453 | public static void write32(Raster raster, Raster alpha, net.sf.image4j.io.LittleEndianOutputStream out) throws IOException { 454 | 455 | int width = raster.getWidth(); 456 | int height = raster.getHeight(); 457 | 458 | // write lines 459 | for (int y = height - 1; y >= 0; y--) { 460 | 461 | // write pixel data for each line 462 | for (int x = 0; x < width; x++) { 463 | 464 | // get RGBA values 465 | int r = raster.getSample(x, y, 0); 466 | int g = raster.getSample(x, y, 1); 467 | int b = raster.getSample(x, y, 2); 468 | int a = alpha.getSample(x, y, 0); 469 | 470 | // write RGBA values 471 | out.writeByte(b); 472 | out.writeByte(g); 473 | out.writeByte(r); 474 | out.writeByte(a); 475 | } 476 | } 477 | } 478 | 479 | /** 480 | * Sets a particular bit in a byte. 481 | * @param bits the source byte 482 | * @param index the index of the bit to set 483 | * @param bit the value for the bit, which should be either 0 or 1. 484 | * @param the resultant byte 485 | */ 486 | private static byte setBit(byte bits, int index, int bit) { 487 | if (bit == 0) { 488 | bits &= ~(1 << (7 - index)); 489 | } else { 490 | bits |= 1 << (7 - index); 491 | } 492 | return bits; 493 | } 494 | 495 | /** 496 | * Sets a particular nibble (4 bits) in a byte. 497 | * @param nibbles the source byte 498 | * @param index the index of the nibble to set 499 | * @param the value for the nibble, which should be in the range 0x0..0xF. 500 | */ 501 | private static byte setNibble(byte nibbles, int index, int nibble) { 502 | nibbles |= (nibble << ((1 - index) * 4)); 503 | 504 | return nibbles; 505 | } 506 | 507 | /** 508 | * Calculates the size in bytes for a colour map with the specified bit count. 509 | * @param sBitCount the bit count, which represents the colour depth 510 | * @return the size of the colour map, in bytes if sBitCount is less than or equal to 8, 511 | * otherwise 0 as colour maps are only used for bitmaps with a colour depth of 8 bits or less. 512 | */ 513 | public static int getColorMapSize(short sBitCount) { 514 | int ret = 0; 515 | if (sBitCount <= 8) { 516 | ret = (1 << sBitCount) * 4; 517 | } 518 | return ret; 519 | } 520 | 521 | } 522 | -------------------------------------------------------------------------------- /src/net/sf/image4j/codec/bmp/BMPImage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * BMPImage.java 3 | * 4 | * Created on February 19, 2007, 8:08 AM 5 | * 6 | * To change this template, choose Tools | Template Manager 7 | * and open the template in the editor. 8 | */ 9 | 10 | package net.sf.image4j.codec.bmp; 11 | 12 | /** 13 | * Contains a decoded BMP image, as well as information about the source encoded image. 14 | * @since 0.7 15 | * @author Ian McDonagh 16 | */ 17 | public class BMPImage { 18 | 19 | protected InfoHeader infoHeader; 20 | protected java.awt.image.BufferedImage image; 21 | 22 | /** 23 | * Creates a new instance of BMPImage 24 | * @param image the decoded image 25 | * @param infoHeader the InfoHeader structure providing information about the source encoded image 26 | */ 27 | public BMPImage(java.awt.image.BufferedImage image, InfoHeader infoHeader) { 28 | this.image = image; 29 | this.infoHeader = infoHeader; 30 | } 31 | 32 | /** 33 | * The InfoHeader structure representing the encoded BMP image. 34 | */ 35 | public InfoHeader getInfoHeader() { 36 | return infoHeader; 37 | } 38 | 39 | /** 40 | * Sets the InfoHeader structure used for encoding the BMP image. 41 | */ 42 | public void setInfoHeader(InfoHeader infoHeader) { 43 | this.infoHeader = infoHeader; 44 | } 45 | 46 | /** 47 | * The decoded BMP image. 48 | */ 49 | public java.awt.image.BufferedImage getImage() { 50 | return image; 51 | } 52 | 53 | /** 54 | * Sets the image to be encoded. 55 | */ 56 | public void setImage(java.awt.image.BufferedImage image) { 57 | this.image = image; 58 | } 59 | 60 | /** 61 | * The width of the BMP image in pixels. 62 | * @return the width of the BMP image, or -1 if unknown 63 | * @since 0.7alpha2 64 | */ 65 | public int getWidth() { 66 | return infoHeader == null ? -1 : infoHeader.iWidth; 67 | } 68 | 69 | /** 70 | * The height of the BMP image in pixels. 71 | * @return the height of the BMP image, or -1 if unknown. 72 | * @since 0.7alpha2 73 | */ 74 | public int getHeight() { 75 | return infoHeader == null ? -1 : infoHeader.iHeight; 76 | } 77 | 78 | /** 79 | * The colour depth of the BMP image (bits per pixel). 80 | * @return the colour depth, or -1 if unknown. 81 | * @since 0.7alpha2 82 | */ 83 | public int getColourDepth() { 84 | return infoHeader == null ? -1 : infoHeader.sBitCount; 85 | } 86 | 87 | /** 88 | * The number of possible colours for the BMP image. 89 | * @return the number of colours, or -1 if unknown. 90 | * @since 0.7alpha2 91 | */ 92 | public int getColourCount() { 93 | int bpp = infoHeader.sBitCount == 32 ? 24 : infoHeader.sBitCount; 94 | return bpp == -1 ? -1 : (int) (1 << bpp); 95 | } 96 | 97 | /** 98 | * Specifies whether this BMP image is indexed, that is, the encoded bitmap uses a colour table. 99 | * If getColourDepth() returns -1, the return value has no meaning. 100 | * @return true if indexed, false if not. 101 | * @since 0.7alpha2 102 | */ 103 | public boolean isIndexed() { 104 | return infoHeader == null ? false : infoHeader.sBitCount <= 8; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/net/sf/image4j/codec/bmp/ColorEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ColorEntry.java 3 | * 4 | * Created on 10 May 2006, 08:29 5 | * 6 | * To change this template, choose Tools | Template Manager 7 | * and open the template in the editor. 8 | */ 9 | 10 | package net.sf.image4j.codec.bmp; 11 | 12 | import java.io.IOException; 13 | 14 | /** 15 | * Represents an RGB colour entry used in the palette of an indexed image (colour depth <= 8). 16 | * @author Ian McDonagh 17 | */ 18 | public class ColorEntry { 19 | 20 | /** 21 | * The red component, which should be in the range 0..255. 22 | */ 23 | public int bRed; 24 | /** 25 | * The green component, which should be in the range 0..255. 26 | */ 27 | public int bGreen; 28 | /** 29 | * The blue component, which should be in the range 0..255. 30 | */ 31 | public int bBlue; 32 | /** 33 | * Unused. 34 | */ 35 | public int bReserved; 36 | 37 | /** 38 | * Reads and creates a colour entry from the source input. 39 | * @param in the source input 40 | * @throws java.io.IOException if an error occurs 41 | */ 42 | public ColorEntry(net.sf.image4j.io.LittleEndianInputStream in) throws IOException { 43 | bBlue = in.readUnsignedByte(); 44 | bGreen = in.readUnsignedByte(); 45 | bRed = in.readUnsignedByte(); 46 | bReserved = in.readUnsignedByte(); 47 | } 48 | 49 | /** 50 | * Creates a colour entry with colour components initialized to 0. 51 | */ 52 | public ColorEntry() { 53 | bBlue = 0; 54 | bGreen = 0; 55 | bRed = 0; 56 | bReserved = 0; 57 | } 58 | 59 | /** 60 | * Creates a colour entry with the specified colour components. 61 | * @param r red component 62 | * @param g green component 63 | * @param b blue component 64 | * @param a unused 65 | */ 66 | public ColorEntry(int r, int g, int b, int a) { 67 | bBlue = b; 68 | bGreen = g; 69 | bRed = r; 70 | bReserved = a; 71 | } 72 | 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/net/sf/image4j/codec/bmp/InfoHeader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * InfoHeader.java 3 | * 4 | * Created on 10 May 2006, 08:10 5 | * 6 | * To change this template, choose Tools | Template Manager 7 | * and open the template in the editor. 8 | */ 9 | 10 | package net.sf.image4j.codec.bmp; 11 | 12 | import java.io.IOException; 13 | import net.sf.image4j.io.LittleEndianOutputStream; 14 | 15 | /** 16 | * Represents a bitmap InfoHeader structure, which provides header information. 17 | * @author Ian McDonagh 18 | */ 19 | public class InfoHeader { 20 | 21 | /** 22 | * The size of this InfoHeader structure in bytes. 23 | */ 24 | public int iSize; 25 | /** 26 | * The width in pixels of the bitmap represented by this InfoHeader. 27 | */ 28 | public int iWidth; 29 | /** 30 | * The height in pixels of the bitmap represented by this InfoHeader. 31 | */ 32 | public int iHeight; 33 | /** 34 | * The number of planes, which should always be 1. 35 | */ 36 | public short sPlanes; 37 | /** 38 | * The bit count, which represents the colour depth (bits per pixel). 39 | * This should be either 1, 4, 8, 24 or 32. 40 | */ 41 | public short sBitCount; 42 | /** 43 | * The compression type, which should be one of the following: 44 | * 49 | */ 50 | public int iCompression; 51 | /** 52 | * The compressed size of the image in bytes, or 0 if iCompression is 0. 53 | */ 54 | public int iImageSize; 55 | /** 56 | * Horizontal resolution in pixels/m. 57 | */ 58 | public int iXpixelsPerM; 59 | /** 60 | * Vertical resolution in pixels/m. 61 | */ 62 | public int iYpixelsPerM; 63 | /** 64 | * Number of colours actually used in the bitmap. 65 | */ 66 | public int iColorsUsed; 67 | /** 68 | * Number of important colours (0 = all). 69 | */ 70 | public int iColorsImportant; 71 | 72 | /** 73 | * Calculated number of colours, based on the colour depth specified by {@link #sBitCount sBitCount}. 74 | */ 75 | public int iNumColors; 76 | 77 | /** 78 | * Creates an InfoHeader structure from the source input. 79 | * @param in the source input 80 | * @throws java.io.IOException if an error occurs 81 | */ 82 | public InfoHeader(net.sf.image4j.io.LittleEndianInputStream in) throws IOException { 83 | //Size of InfoHeader structure = 40 84 | iSize = in.readIntLE(); 85 | 86 | init(in, iSize); 87 | } 88 | 89 | /** 90 | * @since 0.6 91 | */ 92 | public InfoHeader(net.sf.image4j.io.LittleEndianInputStream in, int infoSize) throws IOException { 93 | init(in, infoSize); 94 | } 95 | 96 | /** 97 | * @since 0.6 98 | */ 99 | protected void init(net.sf.image4j.io.LittleEndianInputStream in, int infoSize) throws IOException { 100 | this.iSize = infoSize; 101 | 102 | //Width 103 | iWidth = in.readIntLE(); 104 | //Height 105 | iHeight = in.readIntLE(); 106 | //Planes (=1) 107 | sPlanes = in.readShortLE(); 108 | //Bit count 109 | sBitCount = in.readShortLE(); 110 | 111 | //calculate NumColors 112 | iNumColors = (int) Math.pow(2, sBitCount); 113 | 114 | //Compression 115 | iCompression = in.readIntLE(); 116 | //Image size - compressed size of image or 0 if Compression = 0 117 | iImageSize = in.readIntLE(); 118 | //horizontal resolution pixels/meter 119 | iXpixelsPerM = in.readIntLE(); 120 | //vertical resolution pixels/meter 121 | iYpixelsPerM = in.readIntLE(); 122 | //Colors used - number of colors actually used 123 | iColorsUsed = in.readIntLE(); 124 | //Colors important - number of important colors 0 = all 125 | iColorsImportant = in.readIntLE(); 126 | } 127 | 128 | /** 129 | * Creates an InfoHeader with default values. 130 | */ 131 | public InfoHeader() { 132 | //Size of InfoHeader structure = 40 133 | iSize = 40; 134 | //Width 135 | iWidth = 0; 136 | //Height 137 | iHeight = 0; 138 | //Planes (=1) 139 | sPlanes = 1; 140 | //Bit count 141 | sBitCount = 0; 142 | 143 | //caculate NumColors 144 | iNumColors = 0; 145 | 146 | //Compression 147 | iCompression = BMPConstants.BI_RGB; 148 | //Image size - compressed size of image or 0 if Compression = 0 149 | iImageSize = 0; 150 | //horizontal resolution pixels/meter 151 | iXpixelsPerM = 0; 152 | //vertical resolution pixels/meter 153 | iYpixelsPerM = 0; 154 | //Colors used - number of colors actually used 155 | iColorsUsed = 0; 156 | //Colors important - number of important colors 0 = all 157 | iColorsImportant = 0; 158 | } 159 | 160 | /** 161 | * Creates a copy of the source InfoHeader. 162 | * @param source the source to copy 163 | */ 164 | public InfoHeader(InfoHeader source) { 165 | iColorsImportant = source.iColorsImportant; 166 | iColorsUsed = source.iColorsUsed; 167 | iCompression = source.iCompression; 168 | iHeight = source.iHeight; 169 | iWidth = source.iWidth; 170 | iImageSize = source.iImageSize; 171 | iNumColors = source.iNumColors; 172 | iSize = source.iSize; 173 | iXpixelsPerM = source.iXpixelsPerM; 174 | iYpixelsPerM = source.iYpixelsPerM; 175 | sBitCount = source.sBitCount; 176 | sPlanes = source.sPlanes; 177 | 178 | } 179 | 180 | /** 181 | * Writes the InfoHeader structure to output 182 | * @param out the output to which the structure will be written 183 | * @throws java.io.IOException if an error occurs 184 | */ 185 | public void write(net.sf.image4j.io.LittleEndianOutputStream out) throws IOException { 186 | //Size of InfoHeader structure = 40 187 | out.writeIntLE(iSize); 188 | //Width 189 | out.writeIntLE(iWidth); 190 | //Height 191 | out.writeIntLE(iHeight); 192 | //Planes (=1) 193 | out.writeShortLE(sPlanes); 194 | //Bit count 195 | out.writeShortLE(sBitCount); 196 | 197 | //caculate NumColors 198 | //iNumColors = (int) Math.pow(2, sBitCount); 199 | 200 | //Compression 201 | out.writeIntLE(iCompression); 202 | //Image size - compressed size of image or 0 if Compression = 0 203 | out.writeIntLE(iImageSize); 204 | //horizontal resolution pixels/meter 205 | out.writeIntLE(iXpixelsPerM); 206 | //vertical resolution pixels/meter 207 | out.writeIntLE(iYpixelsPerM); 208 | //Colors used - number of colors actually used 209 | out.writeIntLE(iColorsUsed); 210 | //Colors important - number of important colors 0 = all 211 | out.writeIntLE(iColorsImportant); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/net/sf/image4j/codec/bmp/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | BMP codec implementation, which allows encoding and decoding of MS BMP format. 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/net/sf/image4j/codec/ico/ICOConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ICOConstants.java 3 | * 4 | * Created on May 13, 2006, 8:07 PM 5 | * 6 | * To change this template, choose Tools | Template Manager 7 | * and open the template in the editor. 8 | */ 9 | 10 | package net.sf.image4j.codec.ico; 11 | 12 | /** 13 | * Provides constants used with ICO format. 14 | * @author Ian McDonagh 15 | */ 16 | public class ICOConstants { 17 | 18 | /** 19 | * Indicates that ICO data represents an icon (.ICO). 20 | */ 21 | public static final int TYPE_ICON = 1; 22 | /** 23 | * Indicates that ICO data represents a cursor (.CUR). 24 | */ 25 | public static final int TYPE_CURSOR = 2; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/net/sf/image4j/codec/ico/ICODecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ICODecoder.java 3 | * 4 | * Created on May 9, 2006, 9:31 PM 5 | * 6 | * To change this template, choose Tools | Template Manager 7 | * and open the template in the editor. 8 | */ 9 | 10 | package net.sf.image4j.codec.ico; 11 | 12 | import java.awt.image.*; 13 | import java.io.*; 14 | import java.util.*; 15 | import java.util.logging.*; 16 | 17 | import javax.imageio.ImageIO; 18 | 19 | import net.sf.image4j.codec.bmp.*; 20 | import net.sf.image4j.io.*; 21 | 22 | /** 23 | * Decodes images in ICO format. 24 | * 25 | * @author Ian McDonagh 26 | */ 27 | public class ICODecoder { 28 | 29 | private static Logger log = Logger.getLogger(ICODecoder.class.getName()); 30 | 31 | private static final int PNG_MAGIC = 0x89504E47; 32 | private static final int PNG_MAGIC_LE = 0x474E5089; 33 | private static final int PNG_MAGIC2 = 0x0D0A1A0A; 34 | private static final int PNG_MAGIC2_LE = 0x0A1A0A0D; 35 | 36 | // private java.util.List img; 37 | 38 | private ICODecoder() { 39 | } 40 | 41 | /** 42 | * Reads and decodes the given ICO file. Convenience method equivalent to 43 | * {@link #read(java.io.InputStream) read(new 44 | * java.io.FileInputStream(file))}. 45 | * 46 | * @param file 47 | * the source file to read 48 | * @return the list of images decoded from the ICO data 49 | * @throws java.io.IOException 50 | * if an error occurs 51 | */ 52 | public static java.util.List read(java.io.File file) 53 | throws IOException { 54 | java.io.FileInputStream fin = new java.io.FileInputStream(file); 55 | try { 56 | return read(new BufferedInputStream(fin)); 57 | } finally { 58 | try { 59 | fin.close(); 60 | } catch (IOException ex) { 61 | log.log(Level.FINE, "Failed to close file input for file " 62 | + file); 63 | } 64 | } 65 | } 66 | 67 | /** 68 | * Reads and decodes the given ICO file, together with all metadata. 69 | * Convenience method equivalent to {@link #readExt(java.io.InputStream) 70 | * readExt(new java.io.FileInputStream(file))}. 71 | * 72 | * @param file 73 | * the source file to read 74 | * @return the list of images decoded from the ICO data 75 | * @throws java.io.IOException 76 | * if an error occurs 77 | * @since 0.7 78 | */ 79 | public static java.util.List readExt(java.io.File file) 80 | throws IOException { 81 | java.io.FileInputStream fin = new java.io.FileInputStream(file); 82 | try { 83 | return readExt(new BufferedInputStream(fin)); 84 | } finally { 85 | try { 86 | fin.close(); 87 | } catch (IOException ex) { 88 | log.log(Level.WARNING, "Failed to close file input for file " 89 | + file, ex); 90 | } 91 | } 92 | } 93 | 94 | /** 95 | * Reads and decodes ICO data from the given source. The returned list of 96 | * images is in the order in which they appear in the source ICO data. 97 | * 98 | * @param is 99 | * the source InputStream to read 100 | * @return the list of images decoded from the ICO data 101 | * @throws java.io.IOException 102 | * if an error occurs 103 | */ 104 | public static java.util.List read(java.io.InputStream is) 105 | throws IOException { 106 | java.util.List list = readExt(is); 107 | java.util.List ret = new java.util.ArrayList( 108 | list.size()); 109 | for (int i = 0; i < list.size(); i++) { 110 | ICOImage icoImage = list.get(i); 111 | BufferedImage image = icoImage.getImage(); 112 | ret.add(image); 113 | } 114 | return ret; 115 | } 116 | 117 | private static IconEntry[] sortByFileOffset(IconEntry[] entries) { 118 | List list = Arrays.asList(entries); 119 | Collections.sort(list, new Comparator() { 120 | 121 | @Override 122 | public int compare(IconEntry o1, IconEntry o2) { 123 | return o1.iFileOffset - o2.iFileOffset; 124 | } 125 | }); 126 | return list.toArray(new IconEntry[list.size()]); 127 | } 128 | 129 | /** 130 | * Reads and decodes ICO data from the given source, together with all 131 | * metadata. The returned list of images is in the order in which they 132 | * appear in the source ICO data. 133 | * 134 | * @param is 135 | * the source InputStream to read 136 | * @return the list of images decoded from the ICO data 137 | * @throws java.io.IOException 138 | * if an error occurs 139 | * @since 0.7 140 | */ 141 | public static java.util.List readExt(java.io.InputStream is) 142 | throws IOException { 143 | // long t = System.currentTimeMillis(); 144 | 145 | LittleEndianInputStream in = new LittleEndianInputStream( 146 | new CountingInputStream(is)); 147 | 148 | // Reserved 2 byte =0 149 | short sReserved = in.readShortLE(); 150 | // Type 2 byte =1 151 | short sType = in.readShortLE(); 152 | // Count 2 byte Number of Icons in this file 153 | short sCount = in.readShortLE(); 154 | 155 | // Entries Count * 16 list of icons 156 | IconEntry[] entries = new IconEntry[sCount]; 157 | for (short s = 0; s < sCount; s++) { 158 | entries[s] = new IconEntry(in); 159 | } 160 | // Seems like we don't need this, but you never know! 161 | // entries = sortByFileOffset(entries); 162 | 163 | int i = 0; 164 | // images list of bitmap structures in BMP/PNG format 165 | List ret = new ArrayList(sCount); 166 | 167 | try { 168 | for (i = 0; i < sCount; i++) { 169 | // Make sure we're at the right file offset! 170 | int fileOffset = in.getCount(); 171 | if (fileOffset != entries[i].iFileOffset) { 172 | throw new IOException("Cannot read image #" + i 173 | + " starting at unexpected file offset."); 174 | } 175 | int info = in.readIntLE(); 176 | log.log(Level.FINE, "Image #" + i + " @ " + in.getCount() 177 | + " info = " + EndianUtils.toInfoString(info)); 178 | if (info == 40) { 179 | 180 | // read XOR bitmap 181 | // BMPDecoder bmp = new BMPDecoder(is); 182 | InfoHeader infoHeader = BMPDecoder.readInfoHeader(in, info); 183 | InfoHeader andHeader = new InfoHeader(infoHeader); 184 | andHeader.iHeight = (int) (infoHeader.iHeight / 2); 185 | InfoHeader xorHeader = new InfoHeader(infoHeader); 186 | xorHeader.iHeight = andHeader.iHeight; 187 | 188 | andHeader.sBitCount = 1; 189 | andHeader.iNumColors = 2; 190 | 191 | // for now, just read all the raster data (xor + and) 192 | // and store as separate images 193 | 194 | BufferedImage xor = BMPDecoder.read(xorHeader, in); 195 | // If we want to be sure we've decoded the XOR mask 196 | // correctly, 197 | // we can write it out as a PNG to a temp file here. 198 | // try { 199 | // File temp = File.createTempFile("image4j", ".png"); 200 | // ImageIO.write(xor, "png", temp); 201 | // log.info("Wrote xor mask for image #" + i + " to " 202 | // + temp.getAbsolutePath()); 203 | // } catch (Throwable ex) { 204 | // } 205 | // Or just add it to the output list: 206 | // img.add(xor); 207 | 208 | BufferedImage img = new BufferedImage(xorHeader.iWidth, 209 | xorHeader.iHeight, BufferedImage.TYPE_INT_ARGB); 210 | 211 | ColorEntry[] andColorTable = new ColorEntry[] { 212 | new ColorEntry(255, 255, 255, 255), 213 | new ColorEntry(0, 0, 0, 0) }; 214 | 215 | if (infoHeader.sBitCount == 32) { 216 | // transparency from alpha 217 | // ignore bytes after XOR bitmap 218 | int size = entries[i].iSizeInBytes; 219 | int infoHeaderSize = infoHeader.iSize; 220 | // data size = w * h * 4 221 | int dataSize = xorHeader.iWidth * xorHeader.iHeight * 4; 222 | int skip = size - infoHeaderSize - dataSize; 223 | int skip2 = entries[i].iFileOffset + size 224 | - in.getCount(); 225 | 226 | // ignore AND bitmap since alpha channel stores 227 | // transparency 228 | 229 | if (in.skip(skip, false) < skip && i < sCount - 1) { 230 | throw new EOFException("Unexpected end of input"); 231 | } 232 | // If we skipped less bytes than expected, the AND mask 233 | // is probably badly formatted. 234 | // If we're at the last/only entry in the file, silently 235 | // ignore and continue processing... 236 | 237 | // //read AND bitmap 238 | // BufferedImage and = BMPDecoder.read(andHeader, in, 239 | // andColorTable); 240 | // this.img.add(and); 241 | 242 | WritableRaster srgb = xor.getRaster(); 243 | WritableRaster salpha = xor.getAlphaRaster(); 244 | WritableRaster rgb = img.getRaster(); 245 | WritableRaster alpha = img.getAlphaRaster(); 246 | 247 | for (int y = xorHeader.iHeight - 1; y >= 0; y--) { 248 | for (int x = 0; x < xorHeader.iWidth; x++) { 249 | int r = srgb.getSample(x, y, 0); 250 | int g = srgb.getSample(x, y, 1); 251 | int b = srgb.getSample(x, y, 2); 252 | int a = salpha.getSample(x, y, 0); 253 | rgb.setSample(x, y, 0, r); 254 | rgb.setSample(x, y, 1, g); 255 | rgb.setSample(x, y, 2, b); 256 | alpha.setSample(x, y, 0, a); 257 | } 258 | } 259 | 260 | } else { 261 | BufferedImage and = BMPDecoder.read(andHeader, in, 262 | andColorTable); 263 | // img.add(and); 264 | 265 | // copy rgb 266 | WritableRaster srgb = xor.getRaster(); 267 | WritableRaster rgb = img.getRaster(); 268 | // copy alpha 269 | WritableRaster alpha = img.getAlphaRaster(); 270 | WritableRaster salpha = and.getRaster(); 271 | 272 | for (int y = 0; y < xorHeader.iHeight; y++) { 273 | for (int x = 0; x < xorHeader.iWidth; x++) { 274 | int r, g, b; 275 | int c = xor.getRGB(x, y); 276 | r = (c >> 16) & 0xFF; 277 | g = (c >> 8) & 0xFF; 278 | b = (c) & 0xFF; 279 | // red 280 | rgb.setSample(x, y, 0, r); 281 | // green 282 | rgb.setSample(x, y, 1, g); 283 | // blue 284 | rgb.setSample(x, y, 2, b); 285 | // System.out.println(x+","+y+"="+Integer.toHexString(c)); 286 | // img.setRGB(x, y, c); 287 | 288 | // alpha 289 | int a = and.getRGB(x, y); 290 | alpha.setSample(x, y, 0, a); 291 | } 292 | } 293 | } 294 | // create ICOImage 295 | IconEntry iconEntry = entries[i]; 296 | ICOImage icoImage = new ICOImage(img, infoHeader, iconEntry); 297 | icoImage.setPngCompressed(false); 298 | icoImage.setIconIndex(i); 299 | ret.add(icoImage); 300 | } 301 | // check for PNG magic header and that image height and width = 302 | // 0 = 256 -> Vista format 303 | else if (info == PNG_MAGIC_LE) { 304 | 305 | int info2 = in.readIntLE(); 306 | 307 | if (info2 != PNG_MAGIC2_LE) { 308 | throw new IOException( 309 | "Unrecognized icon format for image #" + i); 310 | } 311 | 312 | IconEntry e = entries[i]; 313 | int size = e.iSizeInBytes - 8; 314 | byte[] pngData = new byte[size]; 315 | /* int count = */in.readFully(pngData); 316 | // if (count != pngData.length) { 317 | // throw new 318 | // IOException("Unable to read image #"+i+" - incomplete PNG compressed data"); 319 | // } 320 | java.io.ByteArrayOutputStream bout = new java.io.ByteArrayOutputStream(); 321 | java.io.DataOutputStream dout = new java.io.DataOutputStream( 322 | bout); 323 | dout.writeInt(PNG_MAGIC); 324 | dout.writeInt(PNG_MAGIC2); 325 | dout.write(pngData); 326 | byte[] pngData2 = bout.toByteArray(); 327 | java.io.ByteArrayInputStream bin = new java.io.ByteArrayInputStream( 328 | pngData2); 329 | javax.imageio.stream.ImageInputStream input = javax.imageio.ImageIO 330 | .createImageInputStream(bin); 331 | javax.imageio.ImageReader reader = getPNGImageReader(); 332 | reader.setInput(input); 333 | java.awt.image.BufferedImage img = reader.read(0); 334 | 335 | // create ICOImage 336 | IconEntry iconEntry = entries[i]; 337 | ICOImage icoImage = new ICOImage(img, null, iconEntry); 338 | icoImage.setPngCompressed(true); 339 | icoImage.setIconIndex(i); 340 | ret.add(icoImage); 341 | } else { 342 | throw new IOException( 343 | "Unrecognized icon format for image #" + i); 344 | } 345 | 346 | /* 347 | * InfoHeader andInfoHeader = new InfoHeader(); 348 | * andInfoHeader.iColorsImportant = 0; andInfoHeader.iColorsUsed 349 | * = 0; andInfoHeader.iCompression = BMPConstants.BI_RGB; 350 | * andInfoHeader.iHeight = xorInfoHeader.iHeight / 2; 351 | * andInfoHeader.iWidth = xorInfoHeader. 352 | */ 353 | } 354 | } catch (IOException ex) { 355 | throw new IOException("Failed to read image # " + i, ex); 356 | } 357 | 358 | // long t2 = System.currentTimeMillis(); 359 | // System.out.println("Loaded ICO file in "+(t2 - t)+"ms"); 360 | 361 | return ret; 362 | } 363 | 364 | private static javax.imageio.ImageReader getPNGImageReader() { 365 | javax.imageio.ImageReader ret = null; 366 | java.util.Iterator itr = javax.imageio.ImageIO 367 | .getImageReadersByFormatName("png"); 368 | if (itr.hasNext()) { 369 | ret = itr.next(); 370 | } 371 | return ret; 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /src/net/sf/image4j/codec/ico/ICOEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ICOEncoder.java 3 | * 4 | * Created on 12 May 2006, 04:08 5 | * 6 | * To change this template, choose Tools | Template Manager 7 | * and open the template in the editor. 8 | */ 9 | 10 | package net.sf.image4j.codec.ico; 11 | 12 | import java.awt.image.BufferedImage; 13 | import java.awt.image.IndexColorModel; 14 | import java.awt.image.Raster; 15 | import java.awt.image.WritableRaster; 16 | import java.io.*; 17 | import java.util.List; 18 | 19 | import javax.imageio.ImageWriter; 20 | 21 | import net.sf.image4j.io.LittleEndianOutputStream; 22 | import net.sf.image4j.codec.bmp.BMPEncoder; 23 | import net.sf.image4j.util.ConvertUtil; 24 | import net.sf.image4j.codec.bmp.InfoHeader; 25 | 26 | /** 27 | * Encodes images in ICO format. 28 | * @author Ian McDonagh 29 | */ 30 | public class ICOEncoder { 31 | 32 | /** Creates a new instance of ICOEncoder */ 33 | private ICOEncoder() { 34 | } 35 | 36 | /** 37 | * Encodes and writes a single image to file without colour depth conversion. 38 | * @param image the source image to encode 39 | * @param file the output file to which the encoded image will be written 40 | * @throws java.io.IOException if an exception occurs 41 | */ 42 | public static void write(BufferedImage image, java.io.File file) throws IOException { 43 | write(image, -1, file); 44 | } 45 | 46 | /** 47 | * Encodes and writes a single image without colour depth conversion. 48 | * @param image the source image to encode 49 | * @param os the output to which the encoded image will be written 50 | * @throws java.io.IOException if an exception occurs 51 | */ 52 | public static void write(BufferedImage image, java.io.OutputStream os) throws IOException { 53 | write(image, -1, os); 54 | } 55 | 56 | /** 57 | * Encodes and writes multiple images without colour depth conversion. 58 | * @param images the list of source images to be encoded 59 | * @param os the output to which the encoded image will be written 60 | * @throws java.io.IOException if an error occurs 61 | */ 62 | public static void write(List images, java.io.OutputStream os) throws IOException { 63 | write(images, null, null, os); 64 | } 65 | 66 | /** 67 | * Encodes and writes multiple images to file without colour depth conversion. 68 | * @param images the list of source images to encode 69 | * @param file the file to which the encoded images will be written 70 | * @throws java.io.IOException if an exception occurs 71 | */ 72 | public static void write(List images, java.io.File file) throws IOException { 73 | write(images, null, file); 74 | } 75 | 76 | /** 77 | * Encodes and writes multiple images to file with the colour depth conversion using the specified values. 78 | * @param images the list of source images to encode 79 | * @param bpp array containing desired colour depths for colour depth conversion 80 | * @param file the output file to which the encoded images will be written 81 | * @throws java.io.IOException if an error occurs 82 | */ 83 | public static void write(List images, int[] bpp, java.io.File file) throws IOException { 84 | write(images, bpp, new java.io.FileOutputStream(file)); 85 | } 86 | 87 | /** 88 | * Encodes and outputs a list of images in ICO format. The first image in the list will be at index #0 in the ICO file, the second at index #1, and so on. 89 | * @param images List of images to encode, which will be output in the order supplied in the list. 90 | * @param bpp Array containing the color depth (bits per pixel) for encoding the corresponding image at each index in the images list. If the array is null, no colour depth conversion will be performed. A colour depth value of -1 at a particular index indicates that no colour depth conversion should be performed for that image. 91 | * @param compress Array containing the compression flag for the corresponding image at each index in the images list. If the array is null, no compression will be peformed. A value of true specifies that compression should be performed, while a value of false specifies that no compression should be performed. 92 | * @param file the file to which the encoded images will be written. 93 | * @throws java.io.IOException if an error occurred. 94 | * @since 0.6 95 | */ 96 | public static void write(List images, int[] bpp, boolean[] compress, java.io.File file) throws IOException { 97 | write(images, bpp, compress, new java.io.FileOutputStream(file)); 98 | } 99 | 100 | /** 101 | * Encodes and writes a single image to file with colour depth conversion using the specified value. 102 | * @param image the source image to encode 103 | * @param bpp the colour depth (bits per pixel) for the colour depth conversion, or -1 if no colour depth conversion should be performed 104 | * @param file the output file to which the encoded image will be written 105 | * @throws java.io.IOException if an error occurs 106 | */ 107 | public static void write(BufferedImage image, int bpp, java.io.File file) throws IOException { 108 | java.io.FileOutputStream fout = new java.io.FileOutputStream(file); 109 | try { 110 | BufferedOutputStream out = new BufferedOutputStream(fout); 111 | write(image, bpp, out); 112 | out.flush(); 113 | } finally { 114 | try { 115 | fout.close(); 116 | } catch (IOException ex) { } 117 | } 118 | } 119 | 120 | /** 121 | * Encodes and outputs a single image in ICO format. 122 | * Convenience method, which calls {@link #write(java.util.List,int[],java.io.OutputStream) write(java.util.List,int[],java.io.OutputStream)}. 123 | * @param image The image to encode. 124 | * @param bpp Colour depth (in bits per pixel) for the colour depth conversion, or -1 if no colour depth conversion should be performed. 125 | * @param os The output to which the encoded image will be written. 126 | * @throws java.io.IOException if an error occurs when trying to write the output. 127 | */ 128 | public static void write(BufferedImage image, int bpp, java.io.OutputStream os) throws IOException { 129 | List list = new java.util.ArrayList(1); 130 | list.add(image); 131 | write(list, new int[] { bpp }, new boolean[] { false }, os); 132 | } 133 | 134 | 135 | /** 136 | * Encodes and outputs a list of images in ICO format. The first image in the list will be at index #0 in the ICO file, the second at index #1, and so on. 137 | * @param images List of images to encode, which will be output in the order supplied in the list. 138 | * @param bpp Array containing the color depth (bits per pixel) for encoding the corresponding image at each index in the images list. If the array is null, no colour depth conversion will be performed. A colour depth value of -1 at a particular index indicates that no colour depth conversion should be performed for that image. 139 | * @param os The output to which the encoded images will be written. 140 | * @throws java.io.IOException if an error occurred. 141 | */ 142 | public static void write(List images, int[] bpp, java.io.OutputStream os) throws IOException { 143 | write(images, bpp, null, os); 144 | } 145 | 146 | /** 147 | * Encodes and outputs a list of images in ICO format. The first image in the list will be at index #0 in the ICO file, the second at index #1, and so on. 148 | * @param images List of images to encode, which will be output in the order supplied in the list. 149 | * @param bpp Array containing the color depth (bits per pixel) for encoding the corresponding image at each index in the images list. If the array is null, no colour depth conversion will be performed. A colour depth value of -1 at a particular index indicates that no colour depth conversion should be performed for that image. 150 | * @param compress Array containing the compression flag for the corresponding image at each index in the images list. If the array is null, no compression will be peformed. A value of true specifies that compression should be performed, while a value of false specifies that no compression should be performed. 151 | * @param os The output to which the encoded images will be written. 152 | * @throws java.io.IOException if an error occurred. 153 | * @since 0.6 154 | */ 155 | public static void write(List images, int[] bpp, boolean[] compress, java.io.OutputStream os) throws IOException { 156 | LittleEndianOutputStream out = new LittleEndianOutputStream(os); 157 | 158 | int count = images.size(); 159 | 160 | //file header 6 161 | writeFileHeader(count, ICOConstants.TYPE_ICON, out); 162 | 163 | //file offset where images start 164 | int fileOffset = 6 + count * 16; 165 | 166 | List infoHeaders = new java.util.ArrayList(count); 167 | 168 | List converted = new java.util.ArrayList(count); 169 | 170 | List compressedImages = null; 171 | if (compress != null) { 172 | compressedImages = new java.util.ArrayList(count); 173 | } 174 | 175 | javax.imageio.ImageWriter pngWriter = null; 176 | 177 | //icon entries 16 * count 178 | for (int i = 0; i < count; i++) { 179 | BufferedImage img = images.get(i); 180 | int b = bpp == null ? -1 : bpp[i]; 181 | //convert image 182 | BufferedImage imgc = b == -1 ? img : convert(img, b); 183 | converted.add(imgc); 184 | //create info header 185 | InfoHeader ih = BMPEncoder.createInfoHeader(imgc); 186 | //create icon entry 187 | IconEntry e = createIconEntry(ih); 188 | 189 | if (compress != null) { 190 | if (compress[i]) { 191 | if (pngWriter == null) { 192 | pngWriter = getPNGImageWriter(); 193 | } 194 | byte[] compressedImage = encodePNG(pngWriter, imgc); 195 | compressedImages.add(compressedImage); 196 | e.iSizeInBytes = compressedImage.length; 197 | } else { 198 | compressedImages.add(null); 199 | } 200 | } 201 | 202 | ih.iHeight *= 2; 203 | 204 | e.iFileOffset = fileOffset; 205 | fileOffset += e.iSizeInBytes; 206 | 207 | e.write(out); 208 | 209 | infoHeaders.add(ih); 210 | } 211 | 212 | //images 213 | for (int i = 0; i < count; i++) { 214 | BufferedImage img = images.get(i); 215 | BufferedImage imgc = converted.get(i); 216 | 217 | if (compress == null || !compress[i]) { 218 | 219 | //info header 220 | InfoHeader ih = infoHeaders.get(i); 221 | ih.write(out); 222 | //color map 223 | if (ih.sBitCount <= 8) { 224 | IndexColorModel icm = (IndexColorModel) imgc.getColorModel(); 225 | BMPEncoder.writeColorMap(icm, out); 226 | } 227 | //xor bitmap 228 | writeXorBitmap(imgc, ih, out); 229 | //and bitmap 230 | writeAndBitmap(img, out); 231 | 232 | } 233 | else { 234 | byte[] compressedImage = compressedImages.get(i); 235 | out.write(compressedImage); 236 | } 237 | 238 | //javax.imageio.ImageIO.write(imgc, "png", new java.io.File("test_"+i+".png")); 239 | } 240 | 241 | } 242 | 243 | /** 244 | * Writes the ICO file header for an ICO containing the given number of images. 245 | * @param count the number of images in the ICO 246 | * @param type one of {@link net.sf.image4j.codec.ico.ICOConstants#TYPE_ICON TYPE_ICON} or 247 | * {@link net.sf.image4j.codec.ico.ICOConstants#TYPE_CURSOR TYPE_CURSOR} 248 | * @param out the output to which the file header will be written 249 | * @throws java.io.IOException if an error occurs 250 | */ 251 | public static void writeFileHeader(int count, int type, LittleEndianOutputStream out) throws IOException { 252 | //reserved 2 253 | out.writeShortLE((short) 0); 254 | //type 2 255 | out.writeShortLE((short) type); 256 | //count 2 257 | out.writeShortLE((short) count); 258 | } 259 | 260 | /** 261 | * Constructs an IconEntry from the given InfoHeader 262 | * structure. 263 | * @param ih the InfoHeader structure from which to construct the IconEntry structure. 264 | * @return the IconEntry structure constructed from the IconEntry structure. 265 | */ 266 | public static IconEntry createIconEntry(InfoHeader ih) { 267 | IconEntry ret = new IconEntry(); 268 | //width 1 269 | ret.bWidth = ih.iWidth == 256 ? 0 : ih.iWidth; 270 | //height 1 271 | ret.bHeight = ih.iHeight == 256 ? 0 : ih.iHeight; 272 | //color count 1 273 | ret.bColorCount = ih.iNumColors >= 256 ? 0 : ih.iNumColors; 274 | //reserved 1 275 | ret.bReserved = 0; 276 | //planes 2 = 1 277 | ret.sPlanes = 1; 278 | //bit count 2 279 | ret.sBitCount = ih.sBitCount; 280 | //sizeInBytes 4 - size of infoHeader + xor bitmap + and bitbmap 281 | int cmapSize = BMPEncoder.getColorMapSize(ih.sBitCount); 282 | int xorSize = BMPEncoder.getBitmapSize(ih.iWidth, ih.iHeight, ih.sBitCount); 283 | int andSize = BMPEncoder.getBitmapSize(ih.iWidth, ih.iHeight, 1); 284 | int size = ih.iSize + cmapSize + xorSize + andSize; 285 | ret.iSizeInBytes = size; 286 | //fileOffset 4 287 | ret.iFileOffset = 0; 288 | return ret; 289 | } 290 | 291 | /** 292 | * Encodes the AND bitmap for the given image according the its alpha channel (transparency) and writes it to the given output. 293 | * @param img the image to encode as the AND bitmap. 294 | * @param out the output to which the AND bitmap will be written 295 | * @throws java.io.IOException if an error occurs. 296 | */ 297 | public static void writeAndBitmap(BufferedImage img, net.sf.image4j.io.LittleEndianOutputStream out) throws IOException { 298 | WritableRaster alpha = img.getAlphaRaster(); 299 | 300 | //indexed transparency (eg. GIF files) 301 | if (img.getColorModel() instanceof IndexColorModel && img.getColorModel().hasAlpha()) { 302 | int w = img.getWidth(); 303 | int h = img.getHeight(); 304 | 305 | int bytesPerLine = BMPEncoder.getBytesPerLine1(w); 306 | 307 | byte[] line = new byte[bytesPerLine]; 308 | 309 | IndexColorModel icm = (IndexColorModel) img.getColorModel(); 310 | Raster raster = img.getRaster(); 311 | 312 | for (int y = h - 1; y >= 0; y--) { 313 | 314 | for (int x = 0; x < w; x++) { 315 | int bi = x / 8; 316 | int i = x % 8; 317 | //int a = alpha.getSample(x, y, 0); 318 | int p = raster.getSample(x, y, 0); 319 | int a = icm.getAlpha(p); 320 | //invert bit since and mask is applied to xor mask 321 | int b = ~a & 1; 322 | line[bi] = setBit(line[bi], i, b); 323 | } 324 | 325 | out.write(line); 326 | } 327 | } 328 | //no transparency 329 | else if (alpha == null) { 330 | int h = img.getHeight(); 331 | int w = img.getWidth(); 332 | //calculate number of bytes per line, including 32-bit padding 333 | int bytesPerLine = BMPEncoder.getBytesPerLine1(w); 334 | 335 | byte[] line = new byte[bytesPerLine]; 336 | for (int i = 0; i < bytesPerLine; i++) { 337 | line[i] = (byte) 0; 338 | } 339 | 340 | for (int y = h - 1; y >= 0; y--) { 341 | out.write(line); 342 | } 343 | } 344 | //transparency (ARGB, etc. eg. PNG) 345 | else { 346 | //BMPEncoder.write1(alpha, cmap, out); 347 | 348 | int w = img.getWidth(); 349 | int h = img.getHeight(); 350 | 351 | int bytesPerLine = BMPEncoder.getBytesPerLine1(w); 352 | 353 | byte[] line = new byte[bytesPerLine]; 354 | 355 | for (int y = h - 1; y >= 0; y--) { 356 | 357 | for (int x = 0; x < w; x++) { 358 | int bi = x / 8; 359 | int i = x % 8; 360 | int a = alpha.getSample(x, y, 0); 361 | //invert bit since and mask is applied to xor mask 362 | int b = ~a & 1; 363 | line[bi] = setBit(line[bi], i, b); 364 | } 365 | 366 | out.write(line); 367 | } 368 | 369 | } 370 | } 371 | 372 | private static byte setBit(byte bits, int index, int bit) { 373 | int mask = 1 << (7 - index); 374 | bits &= ~mask; 375 | bits |= bit << (7 - index); 376 | return bits; 377 | } 378 | 379 | private static void writeXorBitmap(BufferedImage img, InfoHeader ih, LittleEndianOutputStream out) throws IOException { 380 | Raster raster = img.getRaster(); 381 | switch (ih.sBitCount) { 382 | case 1: 383 | BMPEncoder.write1(raster, out); 384 | break; 385 | case 4: 386 | BMPEncoder.write4(raster, out); 387 | break; 388 | case 8: 389 | BMPEncoder.write8(raster, out); 390 | break; 391 | case 24: 392 | BMPEncoder.write24(raster, out); 393 | break; 394 | case 32: 395 | Raster alpha = img.getAlphaRaster(); 396 | BMPEncoder.write32(raster, alpha, out); 397 | break; 398 | } 399 | } 400 | 401 | /** 402 | * Utility method, which converts the given image to the specified colour depth. 403 | * @param img the image to convert. 404 | * @param bpp the target colour depth (bits per pixel) for the conversion. 405 | * @return the given image converted to the specified colour depth. 406 | */ 407 | public static BufferedImage convert(BufferedImage img, int bpp) { 408 | BufferedImage ret = null; 409 | switch (bpp) { 410 | case 1: 411 | ret = ConvertUtil.convert1(img); 412 | break; 413 | case 4: 414 | ret = ConvertUtil.convert4(img); 415 | break; 416 | case 8: 417 | ret = ConvertUtil.convert8(img); 418 | break; 419 | case 24: 420 | int b = img.getColorModel().getPixelSize(); 421 | if (b == 24 || b == 32) { 422 | ret = img; 423 | } else { 424 | ret = ConvertUtil.convert24(img); 425 | } 426 | break; 427 | case 32: 428 | int b2 = img.getColorModel().getPixelSize(); 429 | if (b2 == 24 || b2 == 32) { 430 | ret = img; 431 | } else { 432 | ret = ConvertUtil.convert32(img); 433 | } 434 | break; 435 | } 436 | return ret; 437 | } 438 | 439 | /** 440 | * @since 0.6 441 | */ 442 | private static javax.imageio.ImageWriter getPNGImageWriter() { 443 | javax.imageio.ImageWriter ret = null; 444 | java.util.Iterator itr = javax.imageio.ImageIO.getImageWritersByFormatName("png"); 445 | if (itr.hasNext()) { 446 | ret = itr.next(); 447 | } 448 | return ret; 449 | } 450 | 451 | /** 452 | * @since 0.6 453 | */ 454 | private static byte[] encodePNG(ImageWriter pngWriter, BufferedImage img) throws IOException { 455 | java.io.ByteArrayOutputStream bout = new java.io.ByteArrayOutputStream(); 456 | javax.imageio.stream.ImageOutputStream output = javax.imageio.ImageIO.createImageOutputStream(bout); 457 | pngWriter.setOutput(output); 458 | pngWriter.write(img); 459 | bout.flush(); 460 | byte[] ret = bout.toByteArray(); 461 | return ret; 462 | } 463 | 464 | } 465 | -------------------------------------------------------------------------------- /src/net/sf/image4j/codec/ico/ICOImage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ICOImage.java 3 | * 4 | * Created on February 19, 2007, 8:11 AM 5 | * 6 | * To change this template, choose Tools | Template Manager 7 | * and open the template in the editor. 8 | */ 9 | 10 | package net.sf.image4j.codec.ico; 11 | 12 | import net.sf.image4j.codec.bmp.InfoHeader; 13 | 14 | /** 15 | * Contains a decoded ICO image, as well as information about the source encoded ICO image. 16 | * @since 0.7 17 | * @author Ian McDonagh 18 | */ 19 | public class ICOImage extends net.sf.image4j.codec.bmp.BMPImage { 20 | 21 | protected IconEntry iconEntry; 22 | protected boolean pngCompressed = false; 23 | protected int iconIndex = -1; 24 | 25 | /** 26 | * Creates a new instance of ICOImage 27 | * @param image the BufferedImage decoded from the source ICO image 28 | * @param infoHeader the BMP InfoHeader structure for the BMP encoded ICO image 29 | * @param iconEntry the IconEntry structure describing the ICO image 30 | */ 31 | public ICOImage(java.awt.image.BufferedImage image, net.sf.image4j.codec.bmp.InfoHeader infoHeader, 32 | IconEntry iconEntry) { 33 | super(image, infoHeader); 34 | this.iconEntry = iconEntry; 35 | } 36 | 37 | /** 38 | * The IconEntry associated with this ICOImage, which provides information 39 | * about the image format and encoding. 40 | * @return the IconEntry structure 41 | */ 42 | public IconEntry getIconEntry() { 43 | return iconEntry; 44 | } 45 | 46 | /** 47 | * Sets the IconEntry associated with this ICOImage. 48 | * @param iconEntry the new IconEntry structure to set 49 | */ 50 | public void setIconEntry(IconEntry iconEntry) { 51 | this.iconEntry = iconEntry; 52 | } 53 | 54 | /** 55 | * Specifies whether the encoded image is PNG compressed. 56 | * @return true if the encoded image is PNG compressed, false if it is plain BMP encoded 57 | */ 58 | public boolean isPngCompressed() { 59 | return pngCompressed; 60 | } 61 | 62 | /** 63 | * Sets whether the encoded image is PNG compressed. 64 | * @param pngCompressed true if the encoded image is PNG compressed, false if it is plain BMP encoded 65 | */ 66 | public void setPngCompressed(boolean pngCompressed) { 67 | this.pngCompressed = pngCompressed; 68 | } 69 | 70 | /** 71 | * The InfoHeader structure representing the encoded ICO image. 72 | * @return the InfoHeader structure, or null if there is no InfoHeader structure, which is possible for PNG compressed icons. 73 | */ 74 | public InfoHeader getInfoHeader() { 75 | return super.getInfoHeader(); 76 | } 77 | 78 | /** 79 | * The zero-based index for this ICOImage in the source ICO file or resource. 80 | * @return the index in the source, or -1 if it is unknown. 81 | */ 82 | public int getIconIndex() { 83 | return iconIndex; 84 | } 85 | 86 | /** 87 | * Sets the icon index, which is zero-based. 88 | * @param iconIndex the zero-based icon index, or -1 if unknown. 89 | */ 90 | public void setIconIndex(int iconIndex) { 91 | this.iconIndex = iconIndex; 92 | } 93 | 94 | /** 95 | * The width of the ICO image in pixels. 96 | * @return the width of the ICO image, or -1 if unknown 97 | * @since 0.7alpha2 98 | */ 99 | public int getWidth() { 100 | return iconEntry == null ? -1 : (iconEntry.bWidth == 0 ? 256 : iconEntry.bWidth); 101 | } 102 | 103 | /** 104 | * The height of the ICO image in pixels. 105 | * @return the height of the ICO image, or -1 if unknown. 106 | * @since 0.7alpha2 107 | */ 108 | public int getHeight() { 109 | return iconEntry == null ? -1 : (iconEntry.bHeight == 0 ? 256 : iconEntry.bHeight); 110 | } 111 | 112 | /** 113 | * The colour depth of the ICO image (bits per pixel). 114 | * @return the colour depth, or -1 if unknown. 115 | * @since 0.7alpha2 116 | */ 117 | public int getColourDepth() { 118 | return iconEntry == null ? -1 : iconEntry.sBitCount; 119 | } 120 | 121 | /** 122 | * The number of possible colours for the ICO image. 123 | * @return the number of colours, or -1 if unknown. 124 | * @since 0.7alpha2 125 | */ 126 | public int getColourCount() { 127 | int bpp = iconEntry.sBitCount == 32 ? 24 : iconEntry.sBitCount; 128 | return bpp == -1 ? -1 : (int) (1 << bpp); 129 | } 130 | 131 | /** 132 | * Specifies whether this ICO image is indexed, that is, the encoded bitmap uses a colour table. 133 | * If getColourDepth() returns -1, the return value has no meaning. 134 | * @return true if indexed, false if not. 135 | * @since 0.7alpha2 136 | */ 137 | public boolean isIndexed() { 138 | return iconEntry == null ? false : iconEntry.sBitCount <= 8; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/net/sf/image4j/codec/ico/IconEntry.java: -------------------------------------------------------------------------------- 1 | package net.sf.image4j.codec.ico; 2 | 3 | import java.io.IOException; 4 | import net.sf.image4j.io.LittleEndianInputStream; 5 | 6 | /** 7 | * Represents an IconEntry structure, which contains information about an ICO image. 8 | * @author Ian McDonagh 9 | */ 10 | public class IconEntry { 11 | /** 12 | * The width of the icon image in pixels. 13 | * 0 specifies a width of 256 pixels. 14 | */ 15 | public int bWidth; 16 | /** 17 | * The height of the icon image in pixels. 18 | * 0 specifies a height of 256 pixels. 19 | */ 20 | public int bHeight; 21 | /** 22 | * The number of colours, calculated from {@link #sBitCount sBitCount}. 23 | * 0 specifies a colour count of >= 256. 24 | */ 25 | public int bColorCount; 26 | /** 27 | * Unused. Should always be 0. 28 | */ 29 | public byte bReserved; 30 | /** 31 | * Number of planes, which should always be 1. 32 | */ 33 | public short sPlanes; 34 | /** 35 | * Colour depth in bits per pixel. 36 | */ 37 | public short sBitCount; 38 | /** 39 | * Size of ICO data, which should be the size of (InfoHeader + AND bitmap + XOR bitmap). 40 | */ 41 | public int iSizeInBytes; 42 | /** 43 | * Position in file where the InfoHeader starts. 44 | */ 45 | public int iFileOffset; 46 | 47 | /** 48 | * Creates an IconEntry structure from the source input 49 | * @param in the source input 50 | * @throws java.io.IOException if an error occurs 51 | */ 52 | public IconEntry(LittleEndianInputStream in) throws IOException { 53 | //Width 1 byte Cursor Width (16, 32, 64, 0 = 256) 54 | bWidth = in.readUnsignedByte(); 55 | //Height 1 byte Cursor Height (16, 32, 64, 0 = 256 , most commonly = Width) 56 | bHeight = in.readUnsignedByte(); 57 | //ColorCount 1 byte Number of Colors (2,16, 0=256) 58 | bColorCount = in.readUnsignedByte(); 59 | //Reserved 1 byte =0 60 | bReserved = in.readByte(); 61 | //Planes 2 byte =1 62 | sPlanes = in.readShortLE(); 63 | //BitCount 2 byte bits per pixel (1, 4, 8) 64 | sBitCount = in.readShortLE(); 65 | //SizeInBytes 4 byte Size of (InfoHeader + ANDbitmap + XORbitmap) 66 | iSizeInBytes = in.readIntLE(); 67 | //FileOffset 4 byte FilePos, where InfoHeader starts 68 | iFileOffset = in.readIntLE(); 69 | } 70 | 71 | /** 72 | * Creates and IconEntry structure with default values. 73 | */ 74 | public IconEntry() { 75 | bWidth = 0; 76 | bHeight = 0; 77 | bColorCount = 0; 78 | sPlanes = 1; 79 | bReserved = 0; 80 | sBitCount = 0; 81 | iSizeInBytes = 0; 82 | iFileOffset = 0; 83 | } 84 | 85 | /** 86 | * A string representation of this IconEntry structure. 87 | */ 88 | public String toString() { 89 | StringBuffer sb = new StringBuffer(); 90 | sb.append("width="); 91 | sb.append(bWidth); 92 | sb.append(",height="); 93 | sb.append(bHeight); 94 | sb.append(",bitCount="); 95 | sb.append(sBitCount); 96 | sb.append(",colorCount="+bColorCount); 97 | return sb.toString(); 98 | } 99 | 100 | /** 101 | * Writes the IconEntry structure to output 102 | * @param out the output 103 | * @throws java.io.IOException if an error occurs 104 | */ 105 | public void write(net.sf.image4j.io.LittleEndianOutputStream out) throws IOException { 106 | //Width 1 byte Cursor Width (16, 32 or 64) 107 | out.writeByte(bWidth); 108 | //Height 1 byte Cursor Height (16, 32 or 64 , most commonly = Width) 109 | out.writeByte(bHeight); 110 | //ColorCount 1 byte Number of Colors (2,16, 0=256) 111 | out.writeByte(bColorCount); 112 | //Reserved 1 byte =0 113 | out.writeByte(bReserved); 114 | //Planes 2 byte =1 115 | out.writeShortLE(sPlanes); 116 | //BitCount 2 byte bits per pixel (1, 4, 8) 117 | out.writeShortLE(sBitCount); 118 | //SizeInBytes 4 byte Size of (InfoHeader + ANDbitmap + XORbitmap) 119 | out.writeIntLE(iSizeInBytes); 120 | //FileOffset 4 byte FilePos, where InfoHeader starts 121 | out.writeIntLE(iFileOffset); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/net/sf/image4j/codec/ico/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

9 | ICO codec implementation, which allows encoding and decoding of MS ICO format. 10 |

11 |

12 | ICO images typically contain multiple images of various sizes and colour depths. Thus, 13 | this codec implementation provides methods to decode an ICO image as a list of BufferedImage objects, 14 | and to encode a list of BufferedImage objects as an ICO image. 15 |

16 | 17 | 18 | -------------------------------------------------------------------------------- /src/net/sf/image4j/codec/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Provides functionality which implement encoding and decoding of image formats. By convension, 10 | classes pertaining to a particular image format are contained in a sub-package. 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/net/sf/image4j/example/Test.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Test.java 3 | * 4 | * Created on January 19, 2007, 11:15 AM 5 | * 6 | * To change this template, choose Tools | Template Manager 7 | * and open the template in the editor. 8 | */ 9 | 10 | package net.sf.image4j.example; 11 | 12 | import java.io.IOException; 13 | 14 | /** 15 | * 16 | * @author ian 17 | */ 18 | public class Test { 19 | 20 | public static void main(String[] args) { 21 | 22 | // input and output file names 23 | 24 | String strInFile = "input.ico"; 25 | String strOutFile = "output.ico"; 26 | 27 | try { 28 | 29 | /***** decode ICO and save images as BMP and PNG ****/ 30 | 31 | System.out.println("Decoding ICO file '"+strInFile+"'."); 32 | 33 | java.io.File inFile = new java.io.File(strInFile); 34 | 35 | // load and decode ICO file 36 | 37 | java.util.List images = net.sf.image4j.codec.ico.ICODecoder.read(inFile); 38 | 39 | System.out.println("ICO decoding...OK"); 40 | 41 | // display summary of decoded images 42 | 43 | System.out.println(" image count = "+images.size()); 44 | System.out.println(" image summary:"); 45 | for (int i = 0; i < images.size(); i++) { 46 | java.awt.image.BufferedImage img = images.get(i); 47 | int bpp = img.getColorModel().getPixelSize(); 48 | int width = img.getWidth(); 49 | int height = img.getHeight(); 50 | System.out.println(" # "+ i + ": size="+width+"x"+height+"; colour depth="+bpp+" bpp"); 51 | } 52 | 53 | // save images as separate BMP and PNG files 54 | 55 | System.out.println(" saving separate images:"); 56 | 57 | String format = "png"; 58 | 59 | for (int j = 0; j < images.size(); j++) { 60 | java.awt.image.BufferedImage img = images.get(j); 61 | String name = strInFile + "-"+ j; 62 | java.io.File bmpFile = new java.io.File(name+".bmp"); 63 | java.io.File pngFile = new java.io.File(name+".png"); 64 | 65 | // write BMP 66 | System.out.println(" writing '"+name+".bmp'"); 67 | net.sf.image4j.codec.bmp.BMPEncoder.write(img, bmpFile); 68 | 69 | // write PNG 70 | System.out.println(" writing '"+name+".png'"); 71 | javax.imageio.ImageIO.write(img, format, pngFile); 72 | } 73 | 74 | System.out.println("BMP encoding...OK"); 75 | 76 | /***** reload BMP images *****/ 77 | 78 | System.out.println(" reloading BMP files:"); 79 | 80 | java.util.List images2 = 81 | new java.util.ArrayList(images.size()); 82 | 83 | for (int k = 0; k < images.size(); k++) { 84 | String name = strInFile + "-" + k + ".bmp"; 85 | java.io.File file = new java.io.File(name); 86 | 87 | //read BMP 88 | System.out.println(" reading '"+name+"'"); 89 | java.awt.image.BufferedImage image = net.sf.image4j.codec.bmp.BMPDecoder.read(file); 90 | images2.add(image); 91 | } 92 | 93 | System.out.println("BMP decoding...OK"); 94 | 95 | /***** encode images and save as ICO *****/ 96 | 97 | System.out.println("Encoding ICO file '"+strOutFile+"'."); 98 | 99 | java.io.File outFile = new java.io.File(strOutFile); 100 | 101 | net.sf.image4j.codec.ico.ICOEncoder.write(images, outFile); 102 | 103 | System.out.println("ICO encoding...OK"); 104 | 105 | } catch (IOException ex) { 106 | ex.printStackTrace(); 107 | } 108 | 109 | } 110 | 111 | private static void usage() { 112 | 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/net/sf/image4j/example/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Provides an example for demonstrating usage of various image format codecs. 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/net/sf/image4j/io/CountingDataInput.java: -------------------------------------------------------------------------------- 1 | package net.sf.image4j.io; 2 | 3 | import java.io.DataInput; 4 | 5 | public interface CountingDataInput extends DataInput, CountingInput { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/net/sf/image4j/io/CountingDataInputStream.java: -------------------------------------------------------------------------------- 1 | package net.sf.image4j.io; 2 | 3 | import java.io.*; 4 | 5 | public class CountingDataInputStream extends DataInputStream implements CountingDataInput { 6 | 7 | public CountingDataInputStream(InputStream in) { 8 | super(new CountingInputStream(in)); 9 | } 10 | 11 | @Override 12 | public int getCount() { 13 | return ((CountingInputStream) in).getCount(); 14 | } 15 | 16 | public int skip(int count, boolean strict) throws IOException { 17 | return IOUtils.skip(this, count, strict); 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return getClass().getSimpleName() + "(" +in + ") ["+getCount()+"]"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/net/sf/image4j/io/CountingInput.java: -------------------------------------------------------------------------------- 1 | package net.sf.image4j.io; 2 | 3 | public interface CountingInput { 4 | 5 | int getCount(); 6 | } 7 | -------------------------------------------------------------------------------- /src/net/sf/image4j/io/CountingInputStream.java: -------------------------------------------------------------------------------- 1 | package net.sf.image4j.io; 2 | 3 | import java.io.*; 4 | 5 | public class CountingInputStream extends FilterInputStream { 6 | 7 | private int count; 8 | 9 | public CountingInputStream(InputStream src) { 10 | super(src); 11 | } 12 | 13 | public int getCount() { 14 | return count; 15 | } 16 | 17 | @Override 18 | public int read() throws IOException { 19 | int b = super.read(); 20 | if (b != -1) { 21 | count++; 22 | } 23 | return b; 24 | } 25 | 26 | @Override 27 | public int read(byte[] b, int off, int len) throws IOException { 28 | int r = super.read(b, off, len); 29 | if (r > 0) { 30 | count += r; 31 | } 32 | return r; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/net/sf/image4j/io/EndianUtils.java: -------------------------------------------------------------------------------- 1 | package net.sf.image4j.io; 2 | 3 | /** 4 | * Provides utility methods for endian conversions [big-endian to little-endian; 5 | * little-endian to big-endian]. 6 | * 7 | * @author Ian McDonagh 8 | */ 9 | public class EndianUtils { 10 | 11 | /** 12 | * Reverses the byte order of the source short value 13 | * 14 | * @param value 15 | * the source value 16 | * @return the converted value 17 | */ 18 | public static short swapShort(short value) { 19 | return (short) (((value & 0xFF00) >> 8) | ((value & 0x00FF) << 8)); 20 | } 21 | 22 | /** 23 | * Reverses the byte order of the source int value 24 | * 25 | * @param value 26 | * the source value 27 | * @return the converted value 28 | */ 29 | public static int swapInteger(int value) { 30 | return ((value & 0xFF000000) >> 24) | ((value & 0x00FF0000) >> 8) 31 | | ((value & 0x0000FF00) << 8) | ((value & 0x000000FF) << 24); 32 | } 33 | 34 | /** 35 | * Reverses the byte order of the source long value 36 | * 37 | * @param value 38 | * the source value 39 | * @return the converted value 40 | */ 41 | public static long swapLong(long value) { 42 | return ((value & 0xFF00000000000000L) >> 56) 43 | | ((value & 0x00FF000000000000L) >> 40) 44 | | ((value & 0x0000FF0000000000L) >> 24) 45 | | ((value & 0x000000FF00000000L) >> 8) 46 | | ((value & 0x00000000FF000000L) << 8) 47 | | ((value & 0x0000000000FF0000L) << 24) 48 | | ((value & 0x000000000000FF00L) << 40) 49 | | ((value & 0x00000000000000FFL) << 56); 50 | } 51 | 52 | /** 53 | * Reverses the byte order of the source float value 54 | * 55 | * @param value 56 | * the source value 57 | * @return the converted value 58 | */ 59 | public static float swapFloat(float value) { 60 | int i = Float.floatToIntBits(value); 61 | i = swapInteger(i); 62 | return Float.intBitsToFloat(i); 63 | } 64 | 65 | /** 66 | * Reverses the byte order of the source double value 67 | * 68 | * @param value 69 | * the source value 70 | * @return the converted value 71 | */ 72 | public static double swapDouble(double value) { 73 | long l = Double.doubleToLongBits(value); 74 | l = swapLong(l); 75 | return Double.longBitsToDouble(l); 76 | } 77 | 78 | public static String toHexString(int i, boolean littleEndian, int bytes) { 79 | if (littleEndian) { 80 | i = swapInteger(i); 81 | } 82 | StringBuilder sb = new StringBuilder(); 83 | sb.append(Integer.toHexString(i)); 84 | if (sb.length() % 2 != 0) { 85 | sb.insert(0, '0'); 86 | } 87 | while (sb.length() < bytes * 2) { 88 | sb.insert(0, "00"); 89 | } 90 | return sb.toString(); 91 | } 92 | 93 | public static StringBuilder toCharString(StringBuilder sb, int i, int bytes, char def) { 94 | int shift = 24; 95 | for (int j = 0; j < bytes; j++) { 96 | int b = (i >> shift) & 0xFF; 97 | char c = b < 32 ? def : (char) b; 98 | sb.append(c); 99 | shift -= 8; 100 | } 101 | return sb; 102 | } 103 | 104 | public static String toInfoString(int info) { 105 | StringBuilder sb = new StringBuilder(); 106 | sb.append("Decimal: ").append(info); 107 | sb.append(", Hex BE: ").append(toHexString(info, false, 4)); 108 | sb.append(", Hex LE: ").append(toHexString(info, true, 4)); 109 | sb.append(", String BE: ["); 110 | sb = toCharString(sb, info, 4, '.'); 111 | sb.append(']'); 112 | sb.append(", String LE: ["); 113 | sb = toCharString(sb, swapInteger(info), 4, '.'); 114 | sb.append(']'); 115 | return sb.toString(); 116 | } 117 | } -------------------------------------------------------------------------------- /src/net/sf/image4j/io/IOUtils.java: -------------------------------------------------------------------------------- 1 | package net.sf.image4j.io; 2 | 3 | import java.io.*; 4 | 5 | public class IOUtils { 6 | 7 | public static int skip(InputStream in, int count, boolean strict) throws IOException { 8 | int skipped = 0; 9 | while (skipped < count) { 10 | int b = in.read(); 11 | if (b == -1) { 12 | break; 13 | } 14 | skipped++; 15 | } 16 | if (skipped < count && strict) { 17 | throw new EOFException("Failed to skip " + count 18 | + " bytes in input"); 19 | } 20 | return skipped; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/net/sf/image4j/io/LittleEndianInputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * LittleEndianInputStream.java 3 | * 4 | * Created on 07 November 2006, 08:26 5 | * 6 | * To change this template, choose Tools | Template Manager 7 | * and open the template in the editor. 8 | */ 9 | 10 | package net.sf.image4j.io; 11 | 12 | import java.io.EOFException; 13 | import java.io.IOException; 14 | 15 | /** 16 | * Reads little-endian data from a source InputStream by reversing byte ordering. 17 | * @author Ian McDonagh 18 | */ 19 | public class LittleEndianInputStream extends java.io.DataInputStream implements CountingDataInput { 20 | 21 | /** 22 | * Creates a new instance of LittleEndianInputStream, which will read from the specified source. 23 | * @param in the source InputStream 24 | */ 25 | public LittleEndianInputStream(CountingInputStream in) { 26 | super(in); 27 | } 28 | 29 | @Override 30 | public int getCount() { 31 | return ((CountingInputStream) in).getCount(); 32 | } 33 | 34 | public int skip(int count, boolean strict) throws IOException { 35 | return IOUtils.skip(this, count, strict); 36 | } 37 | 38 | /** 39 | * Reads a little-endian short value 40 | * @throws java.io.IOException if an error occurs 41 | * @return short value with reversed byte order 42 | */ 43 | public short readShortLE() throws IOException { 44 | 45 | int b1 = read(); 46 | int b2 = read(); 47 | 48 | if (b1 < 0 || b2 < 0) { 49 | throw new EOFException(); 50 | } 51 | 52 | short ret = (short) ((b2 << 8) + (b1 << 0)); 53 | 54 | return ret; 55 | } 56 | 57 | /** 58 | * Reads a little-endian int value. 59 | * @throws java.io.IOException if an error occurs 60 | * @return int value with reversed byte order 61 | */ 62 | public int readIntLE() throws IOException { 63 | int b1 = read(); 64 | int b2 = read(); 65 | int b3 = read(); 66 | int b4 = read(); 67 | 68 | if (b1 < -1 || b2 < -1 || b3 < -1 || b4 < -1) { 69 | throw new EOFException(); 70 | } 71 | 72 | int ret = (b4 << 24) + (b3 << 16) + (b2 << 8) + (b1 << 0); 73 | 74 | return ret; 75 | } 76 | 77 | /** 78 | * Reads a little-endian float value. 79 | * @throws java.io.IOException if an error occurs 80 | * @return float value with reversed byte order 81 | */ 82 | public float readFloatLE() throws IOException { 83 | int i = readIntLE(); 84 | 85 | float ret = Float.intBitsToFloat(i); 86 | 87 | return ret; 88 | } 89 | 90 | /** 91 | * Reads a little-endian long value. 92 | * @throws java.io.IOException if an error occurs 93 | * @return long value with reversed byte order 94 | */ 95 | public long readLongLE() throws IOException { 96 | 97 | int i1 = readIntLE(); 98 | int i2 = readIntLE(); 99 | 100 | long ret = ((long)(i1) << 32) + (i2 & 0xFFFFFFFFL); 101 | 102 | return ret; 103 | } 104 | 105 | /** 106 | * Reads a little-endian double value. 107 | * @throws java.io.IOException if an error occurs 108 | * @return double value with reversed byte order 109 | */ 110 | public double readDoubleLE() throws IOException { 111 | 112 | long l = readLongLE(); 113 | 114 | double ret = Double.longBitsToDouble(l); 115 | 116 | return ret; 117 | } 118 | 119 | /** 120 | * @since 0.6 121 | */ 122 | public long readUnsignedInt() throws IOException { 123 | long i1 = readUnsignedByte(); 124 | long i2 = readUnsignedByte(); 125 | long i3 = readUnsignedByte(); 126 | long i4 = readUnsignedByte(); 127 | 128 | long ret = ((i1 << 24) | (i2 << 16) | (i3 << 8) | i4); 129 | 130 | return ret; 131 | } 132 | 133 | /** 134 | * @since 0.6 135 | */ 136 | public long readUnsignedIntLE() throws IOException { 137 | long i1 = readUnsignedByte(); 138 | long i2 = readUnsignedByte(); 139 | long i3 = readUnsignedByte(); 140 | long i4 = readUnsignedByte(); 141 | 142 | long ret = (i4 << 24) | (i3 << 16) | (i2 << 8) | i1; 143 | 144 | return ret; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/net/sf/image4j/io/LittleEndianOutputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * LittleEndianOutputStream.java 3 | * 4 | * Created on 07 November 2006, 08:26 5 | * 6 | * To change this template, choose Tools | Template Manager 7 | * and open the template in the editor. 8 | */ 9 | 10 | package net.sf.image4j.io; 11 | 12 | import java.io.DataOutputStream; 13 | import java.io.IOException; 14 | 15 | /** 16 | * Writes little-endian data to a target OutputStream by reversing byte ordering. 17 | * @author Ian McDonagh 18 | */ 19 | public class LittleEndianOutputStream extends DataOutputStream { 20 | 21 | /** 22 | * Creates a new instance of LittleEndianOutputStream, which will write to the specified target. 23 | * @param out the target OutputStream 24 | */ 25 | public LittleEndianOutputStream(java.io.OutputStream out) { 26 | super(out); 27 | } 28 | 29 | /** 30 | * Writes a little-endian short value 31 | * @param value the source value to convert 32 | * @throws java.io.IOException if an error occurs 33 | */ 34 | public void writeShortLE(short value) throws IOException { 35 | value = EndianUtils.swapShort(value); 36 | super.writeShort(value); 37 | } 38 | 39 | /** 40 | * Writes a little-endian int value 41 | * @param value the source value to convert 42 | * @throws java.io.IOException if an error occurs 43 | */ 44 | public void writeIntLE(int value) throws IOException { 45 | value = EndianUtils.swapInteger(value); 46 | super.writeInt(value); 47 | } 48 | 49 | /** 50 | * Writes a little-endian float value 51 | * @param value the source value to convert 52 | * @throws java.io.IOException if an error occurs 53 | */ 54 | public void writeFloatLE(float value) throws IOException { 55 | value = EndianUtils.swapFloat(value); 56 | super.writeFloat(value); 57 | } 58 | 59 | /** 60 | * Writes a little-endian long value 61 | * @param value the source value to convert 62 | * @throws java.io.IOException if an error occurs 63 | */ 64 | public void writeLongLE(long value) throws IOException { 65 | value = EndianUtils.swapLong(value); 66 | super.writeLong(value); 67 | } 68 | 69 | /** 70 | * Writes a little-endian double value 71 | * @param value the source value to convert 72 | * @throws java.io.IOException if an error occurs 73 | */ 74 | public void writeDoubleLE(double value) throws IOException { 75 | value = EndianUtils.swapDouble(value); 76 | super.writeDouble(value); 77 | } 78 | 79 | /** 80 | * @since 0.6 81 | */ 82 | public void writeUnsignedInt(long value) throws IOException { 83 | int i1 = (int)(value >> 24); 84 | int i2 = (int)((value >> 16) & 0xFF); 85 | int i3 = (int)((value >> 8) & 0xFF); 86 | int i4 = (int)(value & 0xFF); 87 | 88 | write(i1); 89 | write(i2); 90 | write(i3); 91 | write(i4); 92 | } 93 | 94 | /** 95 | * @since 0.6 96 | */ 97 | public void writeUnsignedIntLE(long value) throws IOException { 98 | int i1 = (int)(value >> 24); 99 | int i2 = (int)((value >> 16) & 0xFF); 100 | int i3 = (int)((value >> 8) & 0xFF); 101 | int i4 = (int)(value & 0xFF); 102 | 103 | write(i4); 104 | write(i3); 105 | write(i2); 106 | write(i1); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/net/sf/image4j/io/LittleEndianRandomAccessFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * LittleEndianRandomAccessFile.java 3 | * 4 | * Created on 07 November 2006, 03:04 5 | * 6 | * To change this template, choose Tools | Template Manager 7 | * and open the template in the editor. 8 | */ 9 | 10 | package net.sf.image4j.io; 11 | 12 | import java.io.FileNotFoundException; 13 | import java.io.IOException; 14 | import java.io.RandomAccessFile; 15 | 16 | /** 17 | * Provides endian conversions for input and output with a RandomAccessFile. 18 | * 19 | * This class is currently not in use and has not been tested. 20 | * 21 | * @author Ian McDonagh 22 | */ 23 | public class LittleEndianRandomAccessFile extends RandomAccessFile { 24 | 25 | public LittleEndianRandomAccessFile(java.io.File file, String mode) throws FileNotFoundException { 26 | super(file, mode); 27 | } 28 | 29 | public LittleEndianRandomAccessFile(String name, String mode) throws FileNotFoundException { 30 | super(name, mode); 31 | } 32 | 33 | public short readShortLE() throws IOException { 34 | short ret = super.readShort(); 35 | ret = EndianUtils.swapShort(ret); 36 | return ret; 37 | } 38 | 39 | public int readIntLE() throws IOException { 40 | int ret = super.readInt(); 41 | ret = EndianUtils.swapInteger(ret); 42 | return ret; 43 | } 44 | 45 | public float readFloatLE() throws IOException { 46 | float ret = super.readFloat(); 47 | ret = EndianUtils.swapFloat(ret); 48 | return ret; 49 | } 50 | 51 | public long readLongLE() throws IOException { 52 | long ret = super.readLong(); 53 | ret = EndianUtils.swapLong(ret); 54 | return ret; 55 | } 56 | 57 | public double readDoubleLE() throws IOException { 58 | double ret = super.readDouble(); 59 | ret = EndianUtils.swapDouble(ret); 60 | return ret; 61 | } 62 | 63 | public void writeShortLE(short value) throws IOException { 64 | value = EndianUtils.swapShort(value); 65 | super.writeShort(value); 66 | } 67 | 68 | public void writeIntLE(int value) throws IOException { 69 | value = EndianUtils.swapInteger(value); 70 | super.writeInt(value); 71 | } 72 | 73 | public void writeFloatLE(float value) throws IOException { 74 | value = EndianUtils.swapFloat(value); 75 | super.writeFloat(value); 76 | } 77 | 78 | public void writeLongLE(long value) throws IOException { 79 | value = EndianUtils.swapLong(value); 80 | super.writeLong(value); 81 | } 82 | 83 | public void writeDoubleLE(double value) throws IOException { 84 | value = EndianUtils.swapDouble(value); 85 | super.writeDouble(value); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/net/sf/image4j/io/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Provides special classes for working with I/O, such as endian conversions. 10 | 11 | Java uses big-endian byte ordering, while some formats use little-endian byte ordering. Thus it is necessary to perform endian conversions 12 | when encoding and decoding data in these formats. 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/net/sf/image4j/test/Test.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Test.java 3 | * 4 | * Created on January 19, 2007, 11:15 AM 5 | * 6 | * To change this template, choose Tools | Template Manager 7 | * and open the template in the editor. 8 | */ 9 | 10 | package net.sf.image4j.test; 11 | 12 | import java.io.*; 13 | import java.net.URL; 14 | import java.util.ArrayList; 15 | 16 | import javax.imageio.ImageIO; 17 | 18 | /** 19 | * 20 | * @author Ian McDonagh 21 | */ 22 | public class Test { 23 | 24 | public static void main(String[] args) { 25 | 26 | // input and output file names 27 | 28 | if (args.length < 2) { 29 | System.out.println("Usage:\n\tTest "); 30 | System.exit(1); 31 | } 32 | 33 | String strInFile = args[0]; 34 | String strOutFile = args[1]; 35 | 36 | java.io.InputStream in = null; 37 | try { 38 | 39 | java.util.List images; 40 | 41 | /***** decode ICO and save images as BMP and PNG ****/ 42 | 43 | if (strInFile.startsWith("http:")) { 44 | in = new URL(strInFile).openStream(); 45 | } else { 46 | in = new FileInputStream(strInFile); 47 | } 48 | 49 | if (!strInFile.endsWith(".ico")) { 50 | 51 | images = new ArrayList(1); 52 | images.add(ImageIO.read(in)); 53 | 54 | System.out.println("Read image "+strInFile+"...OK"); 55 | 56 | } else { 57 | 58 | System.out.println("Decoding ICO file '" + strInFile + "'."); 59 | 60 | // load and decode ICO file 61 | 62 | images = net.sf.image4j.codec.ico.ICODecoder.read(in); 63 | System.out.println("ICO decoding...OK"); 64 | 65 | // display summary of decoded images 66 | 67 | System.out.println(" image count = " + images.size()); 68 | System.out.println(" image summary:"); 69 | for (int i = 0; i < images.size(); i++) { 70 | java.awt.image.BufferedImage img = images.get(i); 71 | int bpp = img.getColorModel().getPixelSize(); 72 | int width = img.getWidth(); 73 | int height = img.getHeight(); 74 | System.out.println(" # " + i + ": size=" + width + "x" 75 | + height + "; colour depth=" + bpp + " bpp"); 76 | } 77 | 78 | // save images as separate BMP and PNG files 79 | 80 | System.out.println(" saving separate images:"); 81 | 82 | String format = "png"; 83 | 84 | for (int j = 0; j < images.size(); j++) { 85 | java.awt.image.BufferedImage img = images.get(j); 86 | String name = strOutFile + "-" + j; 87 | java.io.File bmpFile = new java.io.File(name + ".bmp"); 88 | java.io.File pngFile = new java.io.File(name + ".png"); 89 | 90 | // write BMP 91 | System.out.println(" writing '" + name + ".bmp'"); 92 | net.sf.image4j.codec.bmp.BMPEncoder.write(img, bmpFile); 93 | 94 | // write PNG 95 | System.out.println(" writing '" + name + ".png'"); 96 | javax.imageio.ImageIO.write(img, format, pngFile); 97 | } 98 | 99 | System.out.println("BMP encoding...OK"); 100 | 101 | /***** reload BMP images *****/ 102 | 103 | System.out.println(" reloading BMP files:"); 104 | 105 | java.util.List images2 = new java.util.ArrayList( 106 | images.size()); 107 | 108 | for (int k = 0; k < images.size(); k++) { 109 | String name = strOutFile + "-" + k + ".bmp"; 110 | java.io.File file = new java.io.File(name); 111 | 112 | // read BMP 113 | System.out.println(" reading '" + name + "'"); 114 | java.awt.image.BufferedImage image = net.sf.image4j.codec.bmp.BMPDecoder 115 | .read(file); 116 | images2.add(image); 117 | } 118 | 119 | System.out.println("BMP decoding...OK"); 120 | 121 | } 122 | 123 | /***** encode images and save as ICO *****/ 124 | 125 | System.out.println("Encoding ICO file '" + strOutFile + "'."); 126 | 127 | java.io.File outFile = new java.io.File(strOutFile); 128 | 129 | net.sf.image4j.codec.ico.ICOEncoder.write(images, outFile); 130 | 131 | System.out.println("ICO encoding...OK"); 132 | 133 | } catch (IOException ex) { 134 | ex.printStackTrace(); 135 | } finally { 136 | try { 137 | in.close(); 138 | } catch (IOException ex) { } 139 | } 140 | 141 | } 142 | 143 | private static void usage() { 144 | 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /src/net/sf/image4j/test/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Provides an example for demonstrating usage of various image format codecs. 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/net/sf/image4j/util/ConvertUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConvertUtil.java 3 | * 4 | * Created on 12 May 2006, 09:22 5 | * 6 | * To change this template, choose Tools | Template Manager 7 | * and open the template in the editor. 8 | */ 9 | 10 | package net.sf.image4j.util; 11 | 12 | import java.awt.Transparency; 13 | import java.awt.image.BufferedImage; 14 | import java.awt.image.ColorConvertOp; 15 | import java.awt.image.DataBuffer; 16 | import java.awt.image.IndexColorModel; 17 | 18 | /** 19 | * Provides useful methods for converting images from one colour depth to another. 20 | * @author Ian McDonagh 21 | */ 22 | public class ConvertUtil { 23 | 24 | /** 25 | * Converts the source to 1-bit colour depth (monochrome). 26 | * No transparency. 27 | * @param src the source image to convert 28 | * @return a copy of the source image with a 1-bit colour depth. 29 | */ 30 | public static BufferedImage convert1(BufferedImage src) { 31 | IndexColorModel icm = new IndexColorModel( 32 | 1, 2, new byte[] { (byte) 0, (byte) 0xFF }, 33 | new byte[] { (byte) 0, (byte) 0xFF }, 34 | new byte[] { (byte) 0, (byte) 0xFF } 35 | ); 36 | 37 | BufferedImage dest = new BufferedImage( 38 | src.getWidth(), src.getHeight(), 39 | BufferedImage.TYPE_BYTE_BINARY, 40 | icm 41 | ); 42 | 43 | ColorConvertOp cco = new ColorConvertOp( 44 | src.getColorModel().getColorSpace(), 45 | dest.getColorModel().getColorSpace(), 46 | null 47 | ); 48 | 49 | cco.filter(src, dest); 50 | 51 | return dest; 52 | } 53 | 54 | /** 55 | * Converts the source image to 4-bit colour 56 | * using the default 16-colour palette: 57 | *
    58 | *
  • black
  • dark red
  • dark green
  • 59 | *
  • dark yellow
  • dark blue
  • dark magenta
  • 60 | *
  • dark cyan
  • dark grey
  • light grey
  • 61 | *
  • red
  • green
  • yellow
  • blue
  • 62 | *
  • magenta
  • cyan
  • white
  • 63 | *
64 | * No transparency. 65 | * @param src the source image to convert 66 | * @return a copy of the source image with a 4-bit colour depth, with the default colour pallette 67 | */ 68 | public static BufferedImage convert4(BufferedImage src) { 69 | int[] cmap = new int[] { 70 | 0x000000, 0x800000, 0x008000, 0x808000, 71 | 0x000080, 0x800080, 0x008080, 0x808080, 72 | 0xC0C0C0, 0xFF0000, 0x00FF00, 0xFFFF00, 73 | 0x0000FF, 0xFF00FF, 0x00FFFF, 0xFFFFFF 74 | }; 75 | return convert4(src, cmap); 76 | } 77 | 78 | /** 79 | * Converts the source image to 4-bit colour 80 | * using the given colour map. No transparency. 81 | * @param src the source image to convert 82 | * @param cmap the colour map, which should contain no more than 16 entries 83 | * The entries are in the form RRGGBB (hex). 84 | * @return a copy of the source image with a 4-bit colour depth, with the custom colour pallette 85 | */ 86 | public static BufferedImage convert4(BufferedImage src, int[] cmap) { 87 | IndexColorModel icm = new IndexColorModel( 88 | 4, cmap.length, cmap, 0, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE 89 | ); 90 | BufferedImage dest = new BufferedImage( 91 | src.getWidth(), src.getHeight(), 92 | BufferedImage.TYPE_BYTE_BINARY, 93 | icm 94 | ); 95 | ColorConvertOp cco = new ColorConvertOp( 96 | src.getColorModel().getColorSpace(), 97 | dest.getColorModel().getColorSpace(), 98 | null 99 | ); 100 | cco.filter(src, dest); 101 | 102 | return dest; 103 | } 104 | 105 | /** 106 | * Converts the source image to 8-bit colour 107 | * using the default 256-colour palette. No transparency. 108 | * @param src the source image to convert 109 | * @return a copy of the source image with an 8-bit colour depth 110 | */ 111 | public static BufferedImage convert8(BufferedImage src) { 112 | BufferedImage dest = new BufferedImage( 113 | src.getWidth(), src.getHeight(), 114 | BufferedImage.TYPE_BYTE_INDEXED 115 | ); 116 | ColorConvertOp cco = new ColorConvertOp( 117 | src.getColorModel().getColorSpace(), 118 | dest.getColorModel().getColorSpace(), 119 | null 120 | ); 121 | cco.filter(src, dest); 122 | return dest; 123 | } 124 | 125 | /** 126 | * Converts the source image to 24-bit colour (RGB). No transparency. 127 | * @param src the source image to convert 128 | * @return a copy of the source image with a 24-bit colour depth 129 | */ 130 | public static BufferedImage convert24(BufferedImage src) { 131 | BufferedImage dest = new BufferedImage( 132 | src.getWidth(), src.getHeight(), 133 | BufferedImage.TYPE_INT_RGB 134 | ); 135 | ColorConvertOp cco = new ColorConvertOp( 136 | src.getColorModel().getColorSpace(), 137 | dest.getColorModel().getColorSpace(), 138 | null 139 | ); 140 | cco.filter(src, dest); 141 | return dest; 142 | } 143 | 144 | /** 145 | * Converts the source image to 32-bit colour with transparency (ARGB). 146 | * @param src the source image to convert 147 | * @return a copy of the source image with a 32-bit colour depth. 148 | */ 149 | public static BufferedImage convert32(BufferedImage src) { 150 | BufferedImage dest = new BufferedImage( 151 | src.getWidth(), src.getHeight(), 152 | BufferedImage.TYPE_INT_ARGB 153 | ); 154 | ColorConvertOp cco = new ColorConvertOp( 155 | src.getColorModel().getColorSpace(), 156 | dest.getColorModel().getColorSpace(), 157 | null 158 | ); 159 | cco.filter(src, dest); 160 | return dest; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/net/sf/image4j/util/ImageUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ImageUtil.java 3 | * 4 | * Created on 15 May 2006, 01:12 5 | * 6 | * To change this template, choose Tools | Template Manager 7 | * and open the template in the editor. 8 | */ 9 | 10 | package net.sf.image4j.util; 11 | 12 | import java.awt.AlphaComposite; 13 | import java.awt.Composite; 14 | import java.awt.Graphics2D; 15 | import java.awt.Image; 16 | import java.awt.geom.Rectangle2D; 17 | import java.awt.image.BufferedImage; 18 | import java.awt.image.ColorModel; 19 | import java.awt.image.IndexColorModel; 20 | 21 | /** 22 | * Provides utility methods for handling images (java.awt.BufferedImage) 23 | * @author Ian McDonagh 24 | */ 25 | public class ImageUtil { 26 | /** 27 | * Creates a scaled copy of the source image. 28 | * @param src source image to be scaled 29 | * @param width the width for the new scaled image in pixels 30 | * @param height the height for the new scaled image in pixels 31 | * @return a copy of the source image scaled to width x height pixels. 32 | */ 33 | public static BufferedImage scaleImage(BufferedImage src, int width, int height) { 34 | Image scaled = src.getScaledInstance(width, height, 0); 35 | BufferedImage ret = null; 36 | /* 37 | ColorModel cm = src.getColorModel(); 38 | if (cm instanceof IndexColorModel) { 39 | ret = new BufferedImage( 40 | width, height, src.getType(), (IndexColorModel) cm 41 | ); 42 | } 43 | else { 44 | ret = new BufferedImage( 45 | src.getWidth(), src.getHeight(), src.getType() 46 | ); 47 | } 48 | Graphics2D g = ret.createGraphics(); 49 | //clear alpha channel 50 | Composite comp = g.getComposite(); 51 | g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f)); 52 | Rectangle2D.Double d = new Rectangle2D.Double(0,0,ret.getWidth(),ret.getHeight()); 53 | g.fill(d); 54 | g.setComposite(comp); 55 | */ 56 | ret = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 57 | Graphics2D g = ret.createGraphics(); 58 | //copy image 59 | g.drawImage(scaled, 0, 0, null); 60 | return ret; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/net/sf/image4j/util/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Provides utilities for working with images, such as scaling and colour depth conversion. 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/overview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | The image4j library allows you to read and write certain image formats using only Java code. 5 |

6 |

7 | All sub-packages under the codec package provide the implementation for a particular codec. The naming convension 8 | is as follows: 9 |

10 | 11 | 12 | 13 | 14 | 15 |
ElementDescription
package <codec>Package containing codec implementation
class <codec>DecoderDecoder class
class <codec>EncoderEncoder class
16 |

17 | The Decoder and Encoder classes provide various static read and write methods respectively, which 18 | provide the functionality for decoding and encoding images. 19 |

20 | 21 | 22 | --------------------------------------------------------------------------------