EMMA Coverage Report (generated Sat Oct 08 11:41:37 CEST 2011)
[all classes][net.sf.jomic.comic]

COVERAGE SUMMARY FOR SOURCE FILE [ComicSheetLayout.java]

nameclass, %method, %block, %line, %
ComicSheetLayout.java100% (1/1)100% (13/13)81%  (926/1138)91%  (163.5/180)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ComicSheetLayout100% (1/1)100% (13/13)81%  (926/1138)91%  (163.5/180)
isNonRotatableImage (RenderedImage, int): boolean 100% (1/1)30%  (6/20)30%  (0.3/1)
getTargetScalesForBoth (int, int, int, int, int, int): double [] 100% (1/1)57%  (217/384)76%  (42/55)
ComicSheetLayout (): void 100% (1/1)75%  (15/20)95%  (4.8/5)
<static initializer> 100% (1/1)80%  (12/15)80%  (0.8/1)
renderBothButHasOnlyLeft (int): boolean 100% (1/1)80%  (8/10)80%  (0.8/1)
getTargetTopLeft (int, int, int, double []): int [] 100% (1/1)90%  (106/118)93%  (13/14)
renderTo (Graphics2D, int, int, int, ComicSheet, RenderedImage, RenderedImage... 100% (1/1)96%  (193/202)95%  (23.8/25)
getImagesToRender (int, ComicSheet, RenderedImage, ComicSheetRenderSettings):... 100% (1/1)100% (22/22)100% (8/8)
getTargetRotationTransformation (int, int): AffineTransform 100% (1/1)100% (50/50)100% (12/12)
getTargetScales (int, int, int): double [] 100% (1/1)100% (86/86)100% (11/11)
isRotatedOnce (): boolean 100% (1/1)100% (14/14)100% (2/2)
prepare (RenderedImage, RenderedImage, ComicSheetRenderSettings): void 100% (1/1)100% (40/40)100% (11/11)
render (Graphics2D, int, double [], int [], AffineTransform): void 100% (1/1)100% (157/157)100% (34/34)

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/>.
16package net.sf.jomic.comic;
17 
18import java.awt.Graphics2D;
19import java.awt.RenderingHints;
20import java.awt.geom.AffineTransform;
21import java.awt.image.RenderedImage;
22 
23import net.sf.jomic.common.ComicSheetRenderSettings;
24import net.sf.jomic.common.Settings;
25import net.sf.jomic.tools.ImageTools;
26 
27import org.apache.commons.logging.Log;
28import org.apache.commons.logging.LogFactory;
29 
30/**
31 *  Layout specifying how to render the images of a ComicSheet.
32 *
33 * @author    Thomas Aglassinger
34 * @see       net.sf.jomic.comic.ComicSheet
35 */
36public class ComicSheetLayout
37{
38    public static final int RENDER_LEFT = 1;
39    public static final int RENDER_RIGHT = 2;
40    public static final int RENDER_BOTH = 3;
41    static final double NO_SCALE = -1.0;
42 
43    /**
44     *  Threshold for how much heights of two images might differ while still being considered to be
45     *  "similar enough" to be leveled (1.0 means 100%).
46     */
47    private static final double SAME_HEIGHT_THRESHOLD = 0.1;
48 
49    /**
50     *  Threshold for how much ratios of two images might differ before they are not considered
51     *  "similar", and one of them is filled up with a border using the background color  (1.0
52     *  means 100%).
53     */
54    private static final double SAME_RATIO_THRESHOLD = 0.1;
55 
56    private ImageTools imageTools;
57    private Log logger;
58    private /*@ spec_public nullable @*/ ComicSheetRenderSettings renderSettings;
59    private /*@ spec_public nullable @*/ RenderedImage leftImage;
60    private /*@ spec_public nullable @*/ RenderedImage rightImage;
61    private /*@ spec_public @*/ int leftImageHeight;
62    private /*@ spec_public @*/ int leftImageWidth;
63    private /*@ spec_public @*/ int rightImageHeight;
64    private /*@ spec_public @*/ int rightImageWidth;
65    private Settings settings;
66 
67    //@ invariant leftImageHeight >= 0;
68    //@ invariant leftImageWidth >= 0;
69    //@ invariant rightImageHeight >= 0;
70    //@ invariant rightImageWidth >= 0;
71    //@ invariant (rightImage != null) ==> (leftImage != null);
72 
73    public ComicSheetLayout() {
74        logger = LogFactory.getLog(ComicSheetLayout.class);
75        settings = Settings.instance();
76        imageTools = ImageTools.instance();
77    }
78 
79    /**
80     * Set images and settings to be used for layout.
81     */
82    //@ ensures leftImage == newLeftImage;
83    //@ ensures rightImage == newRightImage;
84    //@ ensures renderSettings == newRenderSettings;
85    public void prepare(RenderedImage newLeftImage, /*@ nullable @*/ RenderedImage newRightImage,
86            ComicSheetRenderSettings newRenderSettings) {
87        leftImage = newLeftImage;
88        leftImageWidth = leftImage.getWidth();
89        leftImageHeight = leftImage.getHeight();
90        rightImage = newRightImage;
91        if (rightImage == null) {
92            rightImageWidth = 0;
93            rightImageHeight = 0;
94        } else {
95            rightImageWidth = rightImage.getWidth();
96            rightImageHeight = rightImage.getHeight();
97        }
98        renderSettings = newRenderSettings;
99    }
100 
101    /**
102     * Get transformation to rotate image(s) during <code>render()</code>.
103     */
104    //@ requires targetWidth > 0;
105    //@ requires targetHeight > 0;
106    public AffineTransform getTargetRotationTransformation(int targetWidth, int targetHeight) {
107        AffineTransform result = new AffineTransform();
108        int rotation = renderSettings.getRotation();
109 
110        if (rotation == ImageTools.ROTATE_COUNTERCLOCKWISE) {
111            result.rotate(-Math.PI / 2);
112            result.translate(-targetHeight, 0);
113        } else if (rotation == ImageTools.ROTATE_CLOCKWISE) {
114            result.rotate(Math.PI / 2);
115            result.translate(0, -targetWidth);
116        } else if (rotation == ImageTools.ROTATE_UPSIDE_DOWN) {
117            result.rotate(Math.PI);
118            result.translate(-targetWidth, -targetHeight);
119        } else {
120            //@ assert rotation = ImageTools.ROTATE_NONE;
121        }
122        return result;
123    }
124 
125    /**
126     * Get scales needed to render <code>leftImage</code> and/or
127     * <code>rightImage</code> in a target with size <code>targetWidth</code>
128     * x <code>targetHeight</code>.
129     *
130     * @param imagesToRender
131     *            which images to render: RENDER_LEFT, RENDER_RIGHT, RENDER_BOTH
132     * @return result[0] is the scale for the left image and result[1] for the
133     *         right image or <code>NO_SCALE</code> if there is no such image
134     */
135    //@ requires leftImage != null;
136    //@ requires targetWidth > 0;
137    //@ requires targetHeight > 0;
138    //@ requires (imagesToRender == RENDER_LEFT)
139    //@     || (imagesToRender == RENDER_RIGHT)
140    //@     || (imagesToRender == RENDER_BOTH);
141    //@ requires (imagesToRender == RENDER_RIGHT) ==> (rightImage != null);
142    //@ ensures \result.length == 2;
143    //@ ensures (imagesToRender != RENDER_RIGHT) ==> \result[0] > 0.0;
144    //@ ensures (imagesToRender == RENDER_RIGHT) ==> (\result[0] == NO_SCALE);
145    //@ ensures (imagesToRender == RENDER_LEFT) ==> (\result[1] == NO_SCALE);
146    //@ ensures (rightImage == null) ==> (\result[1] == NO_SCALE);
147    //@ ensures ((rightImage != null) && (imagesToRender != RENDER_LEFT))==> (\result[1] > 0.0);
148    public double[] getTargetScales(int targetWidth, int targetHeight, int imagesToRender) {
149        double[] result = new double[] {NO_SCALE, NO_SCALE};
150        if ((imagesToRender == RENDER_LEFT) || renderBothButHasOnlyLeft(imagesToRender)) {
151            result[0] = imageTools.getScaleToSqueeze(targetWidth, targetHeight,
152                    leftImageWidth, leftImageHeight, renderSettings.getScaleMode());
153        } else if (imagesToRender == RENDER_RIGHT) {
154            result[1] = imageTools.getScaleToSqueeze(targetWidth, targetHeight,
155                    rightImageWidth, rightImageHeight, renderSettings.getScaleMode());
156        } else if (imagesToRender == RENDER_BOTH) {
157            if (renderSettings.getScaleMode().equals(ImageTools.SCALE_ACTUAL)) {
158                result[0] = 1.0;
159                result[1] = 1.0;
160            } else {
161                result = getTargetScalesForBoth(targetWidth, targetHeight,
162                        leftImageWidth, leftImageHeight, rightImageWidth, rightImageHeight);
163            }
164        } else {
165            //@ assert false;
166        }
167        return result;
168    }
169 
170    /**
171     * Get the top left corner in for drawing the sheet in a target with size
172     * <code>targetWidth</code> x <code>targetHeight</code>.
173     */
174    //@ requires leftImage != null;
175    //@ requires targetWidth > 0;
176    //@ requires targetHeight > 0;
177    //@ requires (imagesToRender == RENDER_LEFT)
178    //@     || (imagesToRender == RENDER_RIGHT)
179    //@     || (imagesToRender == RENDER_BOTH);
180    //@ requires (imagesToRender == RENDER_RIGHT) ==> (rightImage != null);
181    //@ ensures \result.length == 2;
182    //@ ensures \result[0] >= 0;
183    //@ ensures \result[1] >= 0;
184    //@ signals_only IllegalArgumentException;
185    public int[] getTargetTopLeft(int targetWidth, int targetHeight, int imagesToRender, double[] scales) {
186        int[] result;
187        double leftScale = scales[0];
188        double rightScale = scales[1];
189        int totalWidth;
190        int totalHeight;
191 
192        if ((imagesToRender == RENDER_LEFT) || renderBothButHasOnlyLeft(imagesToRender)) {
193            totalWidth = (int) Math.ceil(leftImageWidth * leftScale);
194            totalHeight = (int) Math.ceil(leftImageHeight * leftScale);
195        } else if (imagesToRender == RENDER_RIGHT) {
196            totalWidth = (int) Math.ceil(rightImageWidth * rightScale);
197            totalHeight = (int) Math.ceil(rightImageHeight * rightScale);
198        } else if (imagesToRender == RENDER_BOTH) {
199            totalWidth = (int) (Math.ceil(leftImageWidth * leftScale) + Math.ceil(rightImageWidth * rightScale));
200            totalHeight = (int) Math.ceil(Math.max(leftImageHeight * leftScale, rightImageHeight * rightScale));
201        } else {
202            throw new IllegalArgumentException("imagesToRender=" + imagesToRender);
203        }
204        result = new int[] {(targetWidth - totalWidth) / 2, (targetHeight - totalHeight) / 2};
205        return result;
206    }
207 
208    //@ requires (imagesToRender == RENDER_LEFT)
209    //@     || (imagesToRender == RENDER_RIGHT)
210    //@     || (imagesToRender == RENDER_BOTH);
211    private /*@ pure @*/ boolean renderBothButHasOnlyLeft(int imagesToRender) {
212        return (imagesToRender == RENDER_BOTH) && (rightImage == null);
213    }
214 
215    private boolean isRotatedOnce() {
216        int rotation = renderSettings.getRotation();
217 
218        return (rotation == ImageTools.ROTATE_CLOCKWISE) || (rotation == ImageTools.ROTATE_COUNTERCLOCKWISE);
219    }
220 
221    public void renderTo(Graphics2D myGraphics, int screenWidth, int screenHeight, int imageIndex,
222            ComicSheet comicSheet, RenderedImage leftImageToRender, RenderedImage rightImageToRender,
223            ComicSheetRenderSettings newRenderSettings) {
224        int imagesToRender = getImagesToRender(imageIndex, comicSheet, rightImageToRender, newRenderSettings);
225 
226        prepare(leftImageToRender, rightImageToRender, newRenderSettings);
227        assert renderSettings == newRenderSettings;
228 
229        AffineTransform rotationTransformation;
230        double[] scales;
231        int[] topLeftCorner;
232 
233        // Figure out if we really need a rotation
234        boolean isSuppressableImage = isNonRotatableImage(leftImageToRender, imagesToRender);
235        boolean actuallyRotate = !isRotatedOnce() || !isSuppressableImage;
236        logger.debug("rotation=" + renderSettings.getRotation());
237        logger.debug("isRotatedOnce=" + isRotatedOnce());
238        logger.debug("rotateOnlySingle=" + renderSettings.getRotateOnlySinglePortraitImages());
239        logger.debug("twoPageMode=" + renderSettings.getTwoPageMode());
240        logger.debug("isRenderLeft=" + (imagesToRender == RENDER_LEFT));
241        logger.debug("isLandscape=" + imageTools.isLandscape(leftImageToRender));
242        logger.debug("isSuppressableImage=" + isSuppressableImage);
243        logger.debug("actuallyRotate=" + actuallyRotate);
244 
245        if (actuallyRotate) {
246            rotationTransformation = getTargetRotationTransformation(screenWidth, screenHeight);
247        } else {
248            rotationTransformation = new AffineTransform();
249        }
250 
251        if (actuallyRotate && isRotatedOnce()) {
252            scales = getTargetScales(screenHeight, screenWidth, imagesToRender);
253            topLeftCorner = getTargetTopLeft(screenHeight, screenWidth, imagesToRender, scales);
254        } else {
255            scales = getTargetScales(screenWidth, screenHeight, imagesToRender);
256            topLeftCorner = getTargetTopLeft(screenWidth, screenHeight, imagesToRender, scales);
257        }
258 
259        myGraphics.setBackground(settings .getFillColor());
260        myGraphics.clearRect(0, 0, screenWidth, screenHeight);
261        render(myGraphics, imagesToRender, scales, topLeftCorner, rotationTransformation);
262    }
263 
264    // TODO: Rename to isRotatableImage and adjust logic.
265    private boolean isNonRotatableImage(RenderedImage leftImageToRender,
266            int imagesToRender) {
267        return (renderSettings.getRotateOnlySinglePortraitImages()
268                && !renderSettings.getTwoPageMode()
269                && (imagesToRender == RENDER_LEFT)
270                && imageTools.isLandscape(leftImageToRender));
271    }
272 
273    private int getImagesToRender(int imageIndex, ComicSheet comicSheet,
274            RenderedImage rightImageToRender,
275            ComicSheetRenderSettings newRenderSettings) {
276        int imagesToRender;
277        if (rightImageToRender == null) {
278            imagesToRender = RENDER_LEFT;
279        } else if (newRenderSettings.getTwoPageMode()) {
280            imagesToRender = RENDER_BOTH;
281        } else {
282            if (imageIndex == comicSheet.getLeftImageIndex()) {
283                imagesToRender = RENDER_LEFT;
284            } else {
285                imagesToRender = RENDER_RIGHT;
286            }
287        }
288        return imagesToRender;
289    }
290 
291    /**
292     * Render images of comic sheet to <code>target</code>.
293     *
294     * @param target
295     *            where to render images
296     * @param imagesToRender
297     *            which images to render: RENDER_LEFT, RENDER_RIGHT, RENDER_BOTH
298     */
299    //@ requires leftImage != null;
300    //@ requires (imagesToRender == RENDER_LEFT)
301    //@     || (imagesToRender == RENDER_RIGHT)
302    //@     || (imagesToRender == RENDER_BOTH);
303    //@ requires (imagesToRender == RENDER_RIGHT) ==> (rightImage != null);
304    //@ requires scales.length == 2;
305    //@ requires topLeft.length == 2;
306    void render(Graphics2D target, int imagesToRender, double[] scales, int[] topLeft,
307            AffineTransform rotationTransformation) {
308        double leftScale;
309        double rightScale;
310        int topX = topLeft[0];
311        int topY = topLeft[1];
312        RenderedImage leftImageToRender;
313        RenderedImage rightImageToRender;
314 
315        target.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
316        target.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
317        if (renderSettings.getSwapLeftAndRightImage()
318                && (imagesToRender == RENDER_BOTH)
319                && !renderBothButHasOnlyLeft(imagesToRender)) {
320            leftImageToRender = rightImage;
321            leftScale = scales[1];
322            rightImageToRender = leftImage;
323            rightScale = scales[0];
324        } else {
325            leftImageToRender = leftImage;
326            leftScale = scales[0];
327            rightImageToRender = rightImage;
328            rightScale = scales[1];
329        }
330 
331        if ((imagesToRender == RENDER_LEFT) || renderBothButHasOnlyLeft(imagesToRender)) {
332            AffineTransform leftTransformation = new AffineTransform(rotationTransformation);
333 
334            leftTransformation.translate(topX, topY);
335            leftTransformation.scale(leftScale, leftScale);
336            target.drawRenderedImage(leftImageToRender, leftTransformation);
337        } else if (imagesToRender == RENDER_RIGHT) {
338            AffineTransform rightTransformation = new AffineTransform(rotationTransformation);
339 
340            rightTransformation.translate(topX, topY);
341            rightTransformation.scale(rightScale, rightScale);
342            target.drawRenderedImage(rightImageToRender, rightTransformation);
343        } else if (imagesToRender == RENDER_BOTH) {
344            AffineTransform leftTransformation = new AffineTransform(rotationTransformation);
345 
346            leftTransformation.translate(topX, topY);
347            leftTransformation.scale(leftScale, leftScale);
348            target.drawRenderedImage(leftImageToRender, leftTransformation);
349 
350            AffineTransform rightTransformation = new AffineTransform(rotationTransformation);
351            double rightDeltaX = Math.ceil(leftScale * leftImageToRender.getWidth());
352 
353            rightTransformation.translate(topX + rightDeltaX, topY);
354            rightTransformation.scale(rightScale, rightScale);
355            target.drawRenderedImage(rightImageToRender, rightTransformation);
356        } else {
357            //@ assert false;
358        }
359    }
360 
361    //@ requires !renderSettings.getScaleMode().equals(ImageTools.SCALE_ACTUAL);
362    //@ ensures \result.length == 2;
363    private double[] getTargetScalesForBoth(int viewWidth, int viewHeight,
364            int leftWidth, int leftHeight, int rightWidth, int rightHeight) {
365        double[] result = new double[]{NO_SCALE, NO_SCALE};
366        String scaleMode = renderSettings.getScaleMode();
367        double heigthRatio = ((double) leftHeight) / rightHeight;
368        double heightMeasure = Math.abs(heigthRatio - 1);
369        boolean heightsAreSimilar = heightMeasure < SAME_HEIGHT_THRESHOLD;
370        double leftRatio = ((double) leftWidth) / leftHeight;
371        double rightRatio = ((double) rightWidth) / rightHeight;
372        double ratioMeasure = Math.abs(Math.abs(leftRatio / rightRatio) - 1);
373        boolean ratiosAreSimilar = ratioMeasure < SAME_RATIO_THRESHOLD;
374        int actualViewWidth = viewWidth;
375        int actualViewHeight = viewHeight;
376        int leftViewWidth;
377        int leftViewHeight;
378        int rightViewWidth;
379        int rightViewHeight;
380 
381        if (logger.isDebugEnabled()) {
382            logger.debug("size: left=" + leftWidth + "x" + leftHeight + ", right="
383                    + rightWidth + "x" + rightHeight);
384            logger.debug("ratio: left=" + leftRatio + ", right=" + rightRatio
385                    + ", heightMeasure=" + heightMeasure
386                    + ", heightsAreSimilar=" + heightsAreSimilar
387                    + ", ratioMeasure=" + ratioMeasure
388                    + ", ratiosAreSimilar=" + ratiosAreSimilar);
389        }
390        if (heightsAreSimilar || ratiosAreSimilar) {
391            logger.debug("level heights");
392            if (heigthRatio > 1) {
393                leftRatio = 1;
394                rightRatio = heigthRatio;
395            } else {
396                leftRatio = 1.0 / heigthRatio;
397                rightRatio = 1;
398            }
399        } else {
400            logger.debug("level widths");
401            double widthRatio = ((double) leftWidth) / rightWidth;
402 
403            if (widthRatio > 1) {
404                leftRatio = 1;
405                rightRatio = widthRatio;
406            } else {
407                leftRatio = 1.0 / widthRatio;
408                rightRatio = 1;
409            }
410        }
411        if (logger.isDebugEnabled()) {
412            logger.debug("adjusted ratio: left=" + leftRatio + ", right=" + rightRatio);
413        }
414 
415        // Compute the rectangle both images have to share,
416        // independent of the scale mode
417        double adjustedLeftWidth = leftRatio * leftWidth;
418        double adjustedLeftHeight = leftRatio * leftHeight;
419        double adjustedRightWidth = rightRatio * rightWidth;
420        double adjustedRightHeight = rightRatio * rightHeight;
421        double adjustedTotalWidth = adjustedLeftWidth + adjustedRightWidth;
422        double adjustedTotalHeight = Math.max(adjustedLeftHeight, adjustedRightHeight);
423 
424        if (logger.isDebugEnabled()) {
425            logger.debug("adjusted: squeeze=" + adjustedTotalWidth + "x" + adjustedTotalHeight);
426        }
427 
428 
429        if (scaleMode.equals(ImageTools.SCALE_HEIGHT)) {
430            logger.debug("setting viewHeight to maximum");
431            actualViewWidth = Integer.MAX_VALUE;
432        } else if (scaleMode.equals(ImageTools.SCALE_WIDTH)) {
433            logger.debug("setting viewWidth to maximum");
434            actualViewHeight = Integer.MAX_VALUE;
435        }
436 
437        leftViewWidth = (int) Math.round(actualViewWidth * adjustedLeftWidth / adjustedTotalWidth);
438        leftViewHeight = (int) Math.round(actualViewHeight * adjustedLeftHeight / adjustedTotalHeight);
439        rightViewWidth = actualViewWidth - leftViewWidth;
440        rightViewHeight = (int) Math.round(actualViewHeight * adjustedRightHeight / adjustedTotalHeight);
441 
442        if (logger.isDebugEnabled()) {
443            logger.debug("full: view=" + actualViewWidth + "x" + actualViewHeight);
444            logger.debug(
445                    "left: view="
446                    + leftViewWidth
447                    + "x"
448                    + leftViewHeight
449                    + ", adjusted="
450                    + (int) adjustedLeftWidth
451                    + "x"
452                    + (int) adjustedLeftHeight);
453            logger.debug(
454                    "right: view="
455                    + rightViewWidth
456                    + "x"
457                    + rightViewHeight
458                    + ", adjusted="
459                    + (int) adjustedRightWidth
460                    + "x"
461                    + (int) adjustedRightHeight);
462        }
463 
464        result[0] = imageTools.getScaleToSqueeze(
465                leftViewWidth, leftViewHeight, leftWidth, leftHeight, scaleMode);
466        result[1] = imageTools.getScaleToSqueeze(
467                rightViewWidth, rightViewHeight, rightWidth, rightHeight, scaleMode);
468        return result;
469    }
470}

[all classes][net.sf.jomic.comic]
EMMA 2.0.4217 (C) Vladimir Roubtsov