| 1 | // Jomic - a viewer for comic book archives. |
| 2 | // Copyright (C) 2004-2011 Thomas Aglassinger |
| 3 | // |
| 4 | // This program is free software: you can redistribute it and/or modify |
| 5 | // it under the terms of the GNU General Public License as published by |
| 6 | // the Free Software Foundation, either version 3 of the License, or |
| 7 | // (at your option) any later version. |
| 8 | // |
| 9 | // This program is distributed in the hope that it will be useful, |
| 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | // GNU General Public License for more details. |
| 13 | // |
| 14 | // You should have received a copy of the GNU General Public License |
| 15 | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 16 | package net.sf.jomic.comic; |
| 17 | |
| 18 | import java.awt.Dimension; |
| 19 | import java.awt.image.RenderedImage; |
| 20 | import java.io.File; |
| 21 | import java.io.IOException; |
| 22 | |
| 23 | import net.sf.jomic.tools.BasicSettings; |
| 24 | import net.sf.jomic.tools.ImageInfo; |
| 25 | import net.sf.jomic.tools.ImageTools; |
| 26 | import net.sf.jomic.tools.StringTools; |
| 27 | |
| 28 | /** |
| 29 | * Conversion describing how to convert a comic and/or images in it. To actually convert something, |
| 30 | * you need a <code>ConvertComicTask</code>. |
| 31 | * |
| 32 | * @see ConversionBean |
| 33 | * @see ConvertComicTask |
| 34 | * @author Thomas Aglassinger |
| 35 | */ |
| 36 | public class Conversion extends BasicSettings |
| 37 | { |
| 38 | public static final String COMIC_FORMAT_CBZ = "cbz"; |
| 39 | public static final String COMIC_FORMAT_PDF = "pdf"; |
| 40 | public static final String IMAGE_FORMAT_JFIF = "jpeg"; |
| 41 | public static final String IMAGE_FORMAT_PNG = "png"; |
| 42 | public static final String IMAGE_FORMAT_PRESERVE = "preserve"; |
| 43 | static final String PROPERTY_ADD_ONLY_IMAGES = "addNonImages"; |
| 44 | static final String PROPERTY_COMIC_FORMAT = "comicFormat"; |
| 45 | static final String PROPERTY_EXCLUDE_THUMB_PAGES = "excludeThumbPages"; |
| 46 | static final String PROPERTY_IMAGE_FORMAT = "imageFormat"; |
| 47 | static final String PROPERTY_LIMIT_PAGE_SIZE = "limitPageSize"; |
| 48 | static final String PROPERTY_MAX_PAGE_HEIGHT = "maxPageHeight"; |
| 49 | static final String PROPERTY_MAX_PAGE_WIDTH = "maxPageWidth"; |
| 50 | static final String PROPERTY_MAX_THUMB_PAGE_HEIGHT = "maxThumbPageHeight"; |
| 51 | static final String PROPERTY_MAX_THUMB_PAGE_WIDTH = "maxThumbPageWidth"; |
| 52 | private static final boolean DEFAULT_ADD_ONLY_IMAGES = false; |
| 53 | private static final String DEFAULT_COMIC_FORMAT = COMIC_FORMAT_CBZ; |
| 54 | private static final boolean DEFAULT_EXCLUDE_THUMB_PAGES = true; |
| 55 | private static final String DEFAULT_IMAGE_FORMAT = IMAGE_FORMAT_PRESERVE; |
| 56 | private static final boolean DEFAULT_LIMIT_PAGE_SIZE = false; |
| 57 | private static final int DEFAULT_MAX_PAGE_HEIGHT = 1600; |
| 58 | private static final int DEFAULT_MAX_PAGE_WIDTH = 1200; |
| 59 | private static final int DEFAULT_MAX_THUMB_HEIGHT = 200; |
| 60 | private static final int DEFAULT_MAX_THUMB_WIDTH = 160; |
| 61 | private static final String[] VALID_COMIC_FORMATS = new String[]{ |
| 62 | COMIC_FORMAT_CBZ, COMIC_FORMAT_PDF}; |
| 63 | private static final String[] VALID_IMAGE_FORMATS = new String[]{ |
| 64 | IMAGE_FORMAT_PRESERVE, IMAGE_FORMAT_JFIF, IMAGE_FORMAT_PNG}; |
| 65 | |
| 66 | private ImageTools imageTools; |
| 67 | private StringTools stringTools; |
| 68 | |
| 69 | public Conversion() { |
| 70 | super(); |
| 71 | imageTools = ImageTools.instance(); |
| 72 | stringTools = StringTools.instance(); |
| 73 | |
| 74 | for (int i = 0; i < VALID_COMIC_FORMATS.length; i += 1) { |
| 75 | checkValidComicFileFormat(VALID_COMIC_FORMATS[i]); |
| 76 | } |
| 77 | for (int i = 0; i < VALID_IMAGE_FORMATS.length; i += 1) { |
| 78 | checkValidImageFileFormat(VALID_IMAGE_FORMATS[i]); |
| 79 | } |
| 80 | |
| 81 | setDefault(PROPERTY_ADD_ONLY_IMAGES, DEFAULT_ADD_ONLY_IMAGES); |
| 82 | setDefault(PROPERTY_COMIC_FORMAT, DEFAULT_COMIC_FORMAT); |
| 83 | setDefault(PROPERTY_EXCLUDE_THUMB_PAGES, DEFAULT_EXCLUDE_THUMB_PAGES); |
| 84 | setDefault(PROPERTY_IMAGE_FORMAT, DEFAULT_IMAGE_FORMAT); |
| 85 | setDefault(PROPERTY_LIMIT_PAGE_SIZE, DEFAULT_LIMIT_PAGE_SIZE); |
| 86 | setDefault(PROPERTY_MAX_PAGE_HEIGHT, DEFAULT_MAX_PAGE_HEIGHT); |
| 87 | setDefault(PROPERTY_MAX_PAGE_WIDTH, DEFAULT_MAX_PAGE_WIDTH); |
| 88 | setDefault(PROPERTY_MAX_THUMB_PAGE_HEIGHT, DEFAULT_MAX_THUMB_HEIGHT); |
| 89 | setDefault(PROPERTY_MAX_THUMB_PAGE_WIDTH, DEFAULT_MAX_THUMB_WIDTH); |
| 90 | } |
| 91 | |
| 92 | public Conversion(String newComicFormat) { |
| 93 | this(); |
| 94 | checkValidComicFileFormat(newComicFormat); |
| 95 | setComicFormat(newComicFormat); |
| 96 | } |
| 97 | |
| 98 | public void setAddOnlyImages(boolean newValue) { |
| 99 | setBooleanProperty(PROPERTY_ADD_ONLY_IMAGES, newValue); |
| 100 | } |
| 101 | |
| 102 | /** |
| 103 | * Set the format of the target comic. |
| 104 | * |
| 105 | * @param newFileFormat COMIC_FORMAT_CBZ or COMIC_FORMAT_PDF |
| 106 | */ |
| 107 | public void setComicFormat(String newFileFormat) { |
| 108 | checkValidComicFileFormat(newFileFormat); |
| 109 | setProperty(PROPERTY_COMIC_FORMAT, newFileFormat); |
| 110 | } |
| 111 | |
| 112 | public void setExcludeThumbPages(boolean newValue) { |
| 113 | setBooleanProperty(PROPERTY_EXCLUDE_THUMB_PAGES, newValue); |
| 114 | } |
| 115 | |
| 116 | /** |
| 117 | * Set the fomat of the images in the target comic. |
| 118 | * |
| 119 | * @param newImageFormat IMAGE_FORMAT_JFIF, IMAGE_FORMAT_PNG, or IMAGE_FORMAT_PRESERVE |
| 120 | */ |
| 121 | public void setImageFormat(String newImageFormat) { |
| 122 | checkValidImageFileFormat(newImageFormat); |
| 123 | setProperty(PROPERTY_IMAGE_FORMAT, newImageFormat); |
| 124 | } |
| 125 | |
| 126 | public void setLimitImageSize(boolean newValue) { |
| 127 | setBooleanProperty(PROPERTY_LIMIT_PAGE_SIZE, newValue); |
| 128 | } |
| 129 | |
| 130 | public void setMaxImageHeight(int maxImageHeight) { |
| 131 | assert maxImageHeight >= 0; |
| 132 | setIntProperty(PROPERTY_MAX_PAGE_HEIGHT, maxImageHeight); |
| 133 | } |
| 134 | |
| 135 | public void setMaxImageWidth(int maxImageWidth) { |
| 136 | assert maxImageWidth >= 0; |
| 137 | setIntProperty(PROPERTY_MAX_PAGE_WIDTH, maxImageWidth); |
| 138 | } |
| 139 | |
| 140 | public void setMaxThumbHeight(int maxThumbPageHeight) { |
| 141 | assert maxThumbPageHeight >= 0; |
| 142 | setIntProperty(PROPERTY_MAX_THUMB_PAGE_HEIGHT, maxThumbPageHeight); |
| 143 | } |
| 144 | |
| 145 | public void setMaxThumbWidth(int maxThumbPageWidth) { |
| 146 | assert maxThumbPageWidth >= 0; |
| 147 | setIntProperty(PROPERTY_MAX_THUMB_PAGE_WIDTH, maxThumbPageWidth); |
| 148 | } |
| 149 | |
| 150 | public String getComicFormat() { |
| 151 | return getProperty(PROPERTY_COMIC_FORMAT); |
| 152 | } |
| 153 | |
| 154 | /** |
| 155 | * Get the preferred file suffix for a comic according to <code>getComicFormat()</code> |
| 156 | * (without a leading "."). |
| 157 | * |
| 158 | * @return "cbz" or "pdf". |
| 159 | * @see #getComicFormat() |
| 160 | */ |
| 161 | public String getComicFormatSuffix() { |
| 162 | String result; |
| 163 | |
| 164 | if (getComicFormat().equals(Conversion.COMIC_FORMAT_CBZ)) { |
| 165 | result = "cbz"; |
| 166 | } else if (getComicFormat().equals(Conversion.COMIC_FORMAT_PDF)) { |
| 167 | result = "pdf"; |
| 168 | } else { |
| 169 | assert false : "comicFormat=" + stringTools.sourced(getComicFormat()); |
| 170 | result = null; |
| 171 | } |
| 172 | return result; |
| 173 | } |
| 174 | |
| 175 | public String getImageFormat() { |
| 176 | return getProperty(PROPERTY_IMAGE_FORMAT); |
| 177 | } |
| 178 | |
| 179 | /** |
| 180 | * Get target image format name taking in account the original format of the source image (in |
| 181 | * case <code>getImageFormat()</code> is <code>IMAGE_FORMAT_PRESERVE</code>) or the format |
| 182 | * selected by the user. |
| 183 | */ |
| 184 | public String getImageFormatName(String sourceImageFormatName) { |
| 185 | String result; |
| 186 | String imageFormat = getProperty(PROPERTY_IMAGE_FORMAT); |
| 187 | |
| 188 | if (imageFormat.equals(IMAGE_FORMAT_JFIF)) { |
| 189 | result = "jpeg"; |
| 190 | } else if (imageFormat.equals(IMAGE_FORMAT_PNG)) { |
| 191 | result = "png"; |
| 192 | } else if (imageFormat.equals(IMAGE_FORMAT_PRESERVE)) { |
| 193 | result = sourceImageFormatName; |
| 194 | } else { |
| 195 | assert false : "imageFormat = " + imageFormat; |
| 196 | result = null; |
| 197 | } |
| 198 | return result; |
| 199 | } |
| 200 | |
| 201 | public int getMaxImageHeight() { |
| 202 | return getIntProperty(PROPERTY_MAX_PAGE_HEIGHT); |
| 203 | } |
| 204 | |
| 205 | public int getMaxImageWidth() { |
| 206 | return getIntProperty(PROPERTY_MAX_PAGE_WIDTH); |
| 207 | } |
| 208 | |
| 209 | public int getMaxThumbHeight() { |
| 210 | return getIntProperty(PROPERTY_MAX_THUMB_PAGE_HEIGHT); |
| 211 | } |
| 212 | |
| 213 | public int getMaxThumbWidth() { |
| 214 | return getIntProperty(PROPERTY_MAX_THUMB_PAGE_WIDTH); |
| 215 | } |
| 216 | |
| 217 | /** |
| 218 | * Yield image trimmed to maximum size. Landscape images can have twice the maximum width. |
| 219 | * |
| 220 | * @see #getMaxImageHeight() |
| 221 | * @see #getMaxImageWidth() |
| 222 | * @see ImageTools#isLandscape(RenderedImage) |
| 223 | */ |
| 224 | public RenderedImage getTrimmed(RenderedImage image) { |
| 225 | RenderedImage result; |
| 226 | int trimmedWidth; |
| 227 | |
| 228 | if (imageTools.isLandscape(image)) { |
| 229 | trimmedWidth = 2 * getMaxImageWidth(); |
| 230 | } else { |
| 231 | trimmedWidth = getMaxImageWidth(); |
| 232 | } |
| 233 | result = imageTools.getSqueezed(image, trimmedWidth, getMaxImageHeight(), ImageTools.SCALE_FIT); |
| 234 | |
| 235 | return result; |
| 236 | } |
| 237 | |
| 238 | public boolean isAddOnlyImages() { |
| 239 | return getBooleanProperty(PROPERTY_ADD_ONLY_IMAGES); |
| 240 | } |
| 241 | |
| 242 | /** |
| 243 | * Yield <code>true</code> if file should be added to comic archives. |
| 244 | */ |
| 245 | public boolean isAddable(String imageFormat, Dimension imageSize) { |
| 246 | boolean result = (imageFormat != null); |
| 247 | |
| 248 | if (result && isExcludeThumbPages()) { |
| 249 | Dimension portraitSize = getPortraitSize(imageSize); |
| 250 | |
| 251 | result = (portraitSize.width > getMaxThumbWidth()) |
| 252 | && (portraitSize.height > getMaxThumbHeight()); |
| 253 | } |
| 254 | |
| 255 | return result; |
| 256 | } |
| 257 | |
| 258 | public boolean isAddable(ImageInfo imageInfo) { |
| 259 | return !imageInfo.hasError() && isAddable(imageInfo.getFormat(), imageInfo.getSize()); |
| 260 | } |
| 261 | |
| 262 | public boolean isExcludeThumbPages() { |
| 263 | return getBooleanProperty(PROPERTY_EXCLUDE_THUMB_PAGES); |
| 264 | } |
| 265 | |
| 266 | public boolean isLimitImageSize() { |
| 267 | return getBooleanProperty(PROPERTY_LIMIT_PAGE_SIZE); |
| 268 | } |
| 269 | |
| 270 | /** |
| 271 | * Yield the portrait size of <code>size</code> even for landscape dimensions. |
| 272 | */ |
| 273 | private Dimension getPortraitSize(Dimension size) { |
| 274 | Dimension result; |
| 275 | int width; |
| 276 | |
| 277 | if (imageTools.isLandscape(size)) { |
| 278 | width = size.width / 2; |
| 279 | } else { |
| 280 | width = size.width; |
| 281 | } |
| 282 | result = new Dimension(width, size.height); |
| 283 | return result; |
| 284 | } |
| 285 | |
| 286 | public void checkValidComicFileFormat(String supposedFileFormat) { |
| 287 | if (!stringTools.equalsAnyOf(VALID_COMIC_FORMATS, supposedFileFormat, true)) { |
| 288 | // TODO: proper localized error message: "file format is {0}, but must be one of: {1}" |
| 289 | throw new IllegalArgumentException("comicFormat=" + supposedFileFormat); |
| 290 | } |
| 291 | } |
| 292 | |
| 293 | public void checkValidImageFileFormat(String supposedFileFormat) { |
| 294 | if (!stringTools.equalsAnyOf(VALID_IMAGE_FORMATS, supposedFileFormat, true)) { |
| 295 | // TODO: proper localized error message: "file format is {0}, but must be one of: {1}" |
| 296 | throw new IllegalArgumentException("imageFormat=" + supposedFileFormat); |
| 297 | } |
| 298 | } |
| 299 | |
| 300 | /** |
| 301 | * Yield <code>true</code> if the image in <code>imageFile</code> is bigger than the conversion |
| 302 | * allows (provided that <code>isLimitImageSize</code> actually is enabled). |
| 303 | * |
| 304 | * @see #isLimitImageSize() |
| 305 | */ |
| 306 | public boolean needsTrim(File imageFile) |
| 307 | throws IOException { |
| 308 | boolean result = false; |
| 309 | |
| 310 | if (isLimitImageSize()) { |
| 311 | Dimension imageSize = imageTools.getImageDimension(imageFile); |
| 312 | |
| 313 | result = needsTrim(imageSize); |
| 314 | } |
| 315 | return result; |
| 316 | } |
| 317 | |
| 318 | /** |
| 319 | * Yield <code>true</code> if the image with size <code>imageSize</code> is bigger than the |
| 320 | * conversion allows (provided that <code>isLimitImageSize</code> actually is enabled). |
| 321 | * |
| 322 | * @see #isLimitImageSize() |
| 323 | */ |
| 324 | public boolean needsTrim(Dimension imageSize) { |
| 325 | boolean result = false; |
| 326 | |
| 327 | if (isLimitImageSize()) { |
| 328 | Dimension portraitSize = getPortraitSize(imageSize); |
| 329 | boolean widthNeedsTrim = portraitSize.width > getMaxImageWidth(); |
| 330 | boolean heightNeedsTrim = portraitSize.height > getMaxImageHeight(); |
| 331 | |
| 332 | result = widthNeedsTrim || heightNeedsTrim; |
| 333 | } |
| 334 | return result; |
| 335 | } |
| 336 | } |