001/* 002 * Shredzone Commons - pdb 003 * 004 * Copyright (C) 2009 Richard "Shred" Körber 005 * http://commons.shredzone.org 006 * 007 * This program is free software: you can redistribute it and/or modify 008 * it under the terms of the GNU Library General Public License as 009 * published by the Free Software Foundation, either version 3 of the 010 * License, or (at your option) any later version. 011 * 012 * This program is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 015 * GNU General Public License for more details. 016 * 017 * You should have received a copy of the GNU Library General Public License 018 * along with this program. If not, see <http://www.gnu.org/licenses/>. 019 */ 020package org.shredzone.commons.pdb.converter; 021 022import java.awt.image.BufferedImage; 023import java.awt.image.WritableRaster; 024import java.io.ByteArrayOutputStream; 025import java.io.IOException; 026 027import javax.imageio.ImageIO; 028 029import org.shredzone.commons.pdb.PdbDatabase; 030import org.shredzone.commons.pdb.PdbFile; 031import org.shredzone.commons.pdb.appinfo.CategoryAppInfo; 032import org.shredzone.commons.pdb.record.NotepadRecord; 033 034/** 035 * An {@link Converter} that handles notepad entries. 036 * <p> 037 * <em>NOTE:</em> This converter does not work in Android environments. 038 */ 039public class NotepadConverter implements Converter<NotepadRecord, CategoryAppInfo> { 040 041 private static final int FLAG_TITLE = 0x0002; 042 private static final int FLAG_ALARM = 0x0004; 043 044 private static final int TYPE_BITMAP = 0; // Raw bitmap 045 private static final int TYPE_RLE = 1; // Run Length Encoded bitmap 046 private static final int TYPE_PNG = 2; // PNG file 047 048 @Override 049 public boolean isAcceptable(PdbDatabase<NotepadRecord, CategoryAppInfo> database) { 050 return "npadDB".equals(database.getName()) 051 && "npad".equals(database.getCreator()); 052 } 053 054 @Override 055 public NotepadRecord convert(PdbFile reader, int record, int size, int attribute, 056 PdbDatabase<NotepadRecord, CategoryAppInfo> database) throws IOException { 057 long current = reader.getFilePointer(); 058 059 NotepadRecord result = new NotepadRecord(attribute); 060 if (result.isDelete()) { 061 return null; 062 } 063 064 result.setCreated(reader.readDateTimeWords()); 065 result.setModified(reader.readDateTimeWords()); 066 int flags = reader.readUnsignedShort(); 067 068 if ((flags & FLAG_ALARM) != 0) { 069 result.setAlarm(reader.readDateTimeWords()); 070 } 071 072 if ((flags & FLAG_TITLE) != 0) { 073 long start = reader.getFilePointer(); 074 result.setTitle(reader.readTerminatedString()); 075 long end = reader.getFilePointer(); 076 077 // If we're on an odd position, read one padding byte to make it even 078 if (((end - start) % 2) == 1) { 079 reader.readByte(); 080 } 081 } 082 083 reader.readUnsignedInt(); // Offset to the image's end (?) 084 int width = (int) reader.readUnsignedInt(); // Full image width 085 int height = (int) reader.readUnsignedInt(); // Full image height 086 reader.readUnsignedInt(); // Always 1 (?) 087 int type = (int) reader.readUnsignedInt(); // 0 = bitmap, 1 = RLE, 2 = PNG 088 reader.readUnsignedInt(); // Offset to the image's end (?) 089 090 // Read image data 091 int fileSize = size - (int) (reader.getFilePointer() - current); 092 byte[] pngData = new byte[fileSize]; 093 reader.readFully(pngData); 094 095 // Convert image if necessary 096 switch (type) { 097 case TYPE_RLE: //NOSONAR: falls through... 098 pngData = uncompressRle(pngData); 099 100 case TYPE_BITMAP: //NOSONAR: falls through... 101 pngData = convertToPng(width, height, pngData); 102 103 case TYPE_PNG: 104 break; 105 106 default: 107 throw new IOException("unable to handle notepad image type " + type + " at record " + record); 108 } 109 110 result.setImagePng(pngData); 111 112 return result; 113 } 114 115 @Override 116 public CategoryAppInfo convertAppInfo(PdbFile reader, int size, 117 PdbDatabase<NotepadRecord, CategoryAppInfo> database) throws IOException { 118 CategoryAppInfo result = new CategoryAppInfo(); 119 reader.readCategories(result); 120 return result; 121 } 122 123 /** 124 * Uncompresses a Run Length Encoded bitmap. The compression scheme is actually very 125 * simple. The first byte gives the number of times the second byte is written (i.e. 126 * "0x06 0xC0" gives 6 times 0xC0). If the first byte is 0x00, the end of data has 127 * been reached. 128 * 129 * @param width 130 * Image width 131 * @param height 132 * Image height 133 * @param rle 134 * Run Length Encoded data 135 * @return Uncompressed raw bitmap 136 */ 137 private byte[] uncompressRle(byte[] rle) throws IOException { 138 try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { 139 for (int ix = 0; ix < rle.length; ix += 2) { 140 int cnt = rle[ix] & 0xFF; 141 if (cnt == 0x00) break; 142 143 byte data = rle[ix+1]; 144 while (cnt-- > 0) { 145 baos.write(data); 146 } 147 } 148 149 return baos.toByteArray(); 150 } 151 } 152 153 /** 154 * Converts a raw bitmap to a PNG file. 155 * 156 * @param width 157 * Image width 158 * @param height 159 * Image height 160 * @param bitmap 161 * Raw bitmap to be converted 162 * @return PNG file containing that bitmap 163 */ 164 private byte[] convertToPng(int width, int height, byte[] bitmap) throws IOException { 165 BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY); 166 WritableRaster raster = image.getRaster(); 167 168 // Make the width a multiple of 16 first 169 int bytesPerRow = ((((width - 1) / 16) + 1) * 16) / 8; 170 171 for (int w = 0; w < width; w++) { 172 for (int h = 0; h < height; h++) { 173 byte data = bitmap[(h * bytesPerRow) + (w / 8)]; 174 if ((data & (0x80 >> (w % 8))) == 0) { //NOSONAR: int promotion is safe 175 raster.setSample(w,h,0,1); 176 } 177 } 178 } 179 180 try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { 181 ImageIO.write(image, "PNG", baos); 182 return baos.toByteArray(); 183 } 184 } 185 186}