EMMA Coverage Report (generated Sun Apr 20 22:38:01 CEST 2008)
[all classes][net.sf.jomic.comic]

COVERAGE SUMMARY FOR SOURCE FILE [ComicSheetLayout.java]

nameclass, %method, %block, %line, %
ComicSheetLayout.java100% (1/1)100% (13/13)94%  (1067/1130)95%  (170.5/180)

COVERAGE BREAKDOWN BY CLASS AND METHOD

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

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