Bug 47555

Summary: Bottom part of files transparent at small sizes
Product: Batik - Now in Jira Reporter: fibonacci.prower
Component: SVG RasterizerAssignee: Batik Developer's Mailing list <batik-dev>
Status: NEW ---    
Severity: normal    
Priority: P2    
Version: 1.7   
Target Milestone: ---   
Hardware: PC   
OS: Linux   
Attachments: testcase mentioned in the bug report

Description fibonacci.prower 2009-07-21 14:17:33 UTC
Created attachment 24016 [details]
testcase mentioned in the bug report

Just as the title says, sometimes when rasterising an SVG file at small sizes, the bottom row of the raster image appears completely transparent.

Let's take for example the following file:

<svg xmlns="http://www.w3.org/2000/svg" height="10" width="19">
<rect width="19" height="10" fill="lime"/>
</svg>

Obviously, this image is fully green (ff0000), and so should be any rasterisation of this image. However, when rasterising it with Batik at a width of 22px (rasterizer -w 22 -m image/png file.svg), all pizels on the bottom row of the resulting PNG file are completely transparent.
Needless to say, this behaviour makes it difficult to align images properly or put a border around one, as MediaWiki does.
Comment 1 Helder Magalhães 2009-07-21 15:54:19 UTC
(In reply to comment #0)
> Let's take for example the following file:
> 
> <svg xmlns="http://www.w3.org/2000/svg" height="10" width="19">
> <rect width="19" height="10" fill="lime"/>
> </svg>
> 
> Obviously, this image is fully green (ff0000)

Yes, I agree with this part.


> and so should be any
> rasterisation of this image.

No, I don't agree with this. I guess there might be some confusion here between the source image and the target canvas where the image is to be "displayed" (an image buffer written in a file, in this particular case).

For example, the following (based in the attached sample) will fit the canvas (using the a combination of "width" and "height" set to "100%" and the "viewBox" [1] set coherently): 

<svg xmlns="http://www.w3.org/2000/svg"
    width="100%" height="100%" viewBox="0 0 19 10">
  <rect width="19" height="10" fill="lime"/>
</svg>

Note that the image is increased until the width *or* height touches the canvas border. And the following will cause the image to stretch (possibly with distortion) until the canvas is completely filled (note the "preserveAspectRatio" [2] set to "none"):

<svg xmlns="http://www.w3.org/2000/svg"
    width="100%" height="100%" viewBox="0 0 19 10" preserveAspectRatio="none">
  <rect width="19" height="10" fill="lime"/>
</svg>


> Needless to say, this behaviour makes it difficult to align images properly
> or put a border around one, as MediaWiki does.

I understand the use case here but am not sure what is the problem here: Batik's behavior is, as far as I know, compliant to the specification regarding this. I'd say that wisely choosing rasterization hints and/or slightly rework (probably a copy of) the original SVG source before rasterizing may help assuring that the image fits the target (rasterization) canvas.


Please detail a bit on the problem (marking as need info in order to try obtaining it), as currently this issue seems to be invalid in my opinion (and please mark it so if you end up agreeing with the above). ;-)


[1] http://www.w3.org/TR/SVG11/coords.html#ViewBoxAttribute
[2] http://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute
Comment 2 fibonacci.prower 2009-07-21 16:31:42 UTC
In the two test cases you proposed, I get a 22×400px image when rasterising it. I don't know where does the program get the 400px information from, but I sure didn't specify it - not in the command line, and certainly not in the SVG file!

Anyway, let's suppose for a moment Batik is rendering the SVG image in the specified canvas - which was never fully specified by the way, only its width was.
Let's further suppose that Batik stretches the image until its width touches the canvas border (not its height - remember, the height of the canvas was never fully specified!)
In this particular test case (19px wide image rasterised as 22px wide) the height of the SVG rasterisation should be 220/19px, or approximately 11.58 pixels. For some reason, the program chose to approximate that as 12px for the height of the canvas, but 11px for the height of the rectangle (the bottom line is fully 100% transparent - #00000000 pixels - not even a bit green).
The problem isn't, then, that Batik leaves a transparent line for no reason, but that it chooses the size of the canvas badly for no apparent reason, which is not invalid.

How do I ensure that the image fits the canvas, then?
Comment 3 Thomas Deweese 2009-07-21 18:11:43 UTC
The 'fully' transparent bottom pixels is caused by the implicit
clip path of the root SVG element (try adding 'overflow="visible"').

Clip path's in SVG are generally 'hard edged' so the clip either
needs to be at '11' or at '12'.  The G2D rasterizer decides on 11,
the rasterizer rounds to the nearest pixel.

At the same time the ImageTranscoder rounds the width and height to
the nearest integer (12).

The issue here is that if one were to change the ImageTranscoder
to always round down, then someone would complain that we cut off
their 1 pixel high stroke around the image because it only
covered .99 of the bottom pixel, etc.

A perhaps better solution would be to integerize the width and
height before applying the viewing transform then you could 
allow for the slightly non-uniform scaling needed to perfectly
align your viewbox with the pixel grid with the preserveAspectRatio
attribute:

<svg xmlns="http://www.w3.org/2000/svg" height="10" width="19"
     viewBox="0 0 19 10" preserveAspectRatio="none">
<rect width="19" height="10" fill="lime"/>
</svg>

Index: sources/org/apache/batik/transcoder/image/ImageTranscoder.java
===================================================================
--- sources/org/apache/batik/transcoder/image/ImageTranscoder.java      (revision 796594)
+++ sources/org/apache/batik/transcoder/image/ImageTranscoder.java      (working copy)
@@ -75,6 +75,13 @@
     protected ImageTranscoder() {
     }
 
+    protected void setImageSize(float docWidth, float docHeight) {
+        super.setImageSize(docWidth, docHeight);
+
+        width  = (int)(width + 0.5f);
+        height = (int)(height + 0.5f);
+    }
+

------------------

   That said I think you are just asking for trouble trying to rasterize
content that doesn't match your desired pixel grid if you require pixle
perfect rendering.
Comment 4 fibonacci.prower 2009-07-22 02:04:39 UTC
Well, I'm not asking for pixel-perfect rendering, just for consistent behaviour so my rasterised files align nicely.
Perhaps ImageTranscoder shouldn't always round down for the reason you say (I can imagine that being a problem), but both that AND the G2D rasteriser should round to the nearest integer - the important thing being that they both round in the same way.
I don't know enough about the Batik source code (or even Java) to see if that's what your patch does.
Comment 5 Thomas Deweese 2009-07-22 02:42:04 UTC
(In reply to comment #4)
> Well, I'm not asking for pixel-perfect rendering, just for consistent behaviour
> so my rasterised files align nicely.

   You are asking for pixel-perfect rendering.  The Batik renderinging
is effectively "off" by .57 of a pixel.  I understand the desire for your
rasterized files to align nicely but you should understand that what you
are asking for is not as simple as you seem to think.

   What I don't understand is why you would describe content as
being 19x10 and then be surprise when the rendered version doesn't
exactly match the pixel grid at 22x12.  Why don't you simply write
your content to be 22x12 if that is what you want it rendered at?

> Perhaps ImageTranscoder shouldn't always round down for the reason you say (I
> can imagine that being a problem), but both that AND the G2D rasteriser should
> round to the nearest integer - 

   I don't control the G2D (it's from the JDK java.awt.Graphics2D), if you 
want to file a bug with Sun/Oracle that would be fine with me ;)

> the important thing being that they both round in the same way.

   It is a fools errand to try to make these match in all cases.
Unless the clip and the rasterized image bounds are the same 
(and there are reasons in the SVG standard why they aren't) 
there will essentially always be some cases around .5 where they
go in different directions.

> I don't know enough about the Batik source code (or even Java) to see 
> if that's what your patch does.

    It is not what my patch does.  My patch makes it easier for you
to stretch your 19x10 content to a 22x12 rectangle which requires
scaling horizontally by 22/19 = 1.158 and vertically by 12/10 = 1.2