Opened 18 years ago

Last modified 18 years ago

#1701 reopened defect

Outline Antialiased labels eat too many colors of the palette

Reported by: arovira@… Owned by: sdlime
Priority: high Milestone:
Component: MapServer C Library Version: unspecified
Severity: normal Keywords:
Cc: pspencer@…, arovira@…

Description

When mapserver uses a font with antialias and outlinecolor to draw labels, many 
colors are allocated in the palette, and some not used at end. Problems arises 
when other layers with images are drawed over because the max. number of 
palette colors are reached.

The problem is related of how mapserver renders outline fonts. It draw 
the label one pixel displaced in each direction with the outline color
and then draws the label. When rendering this displaced labels, if antialias is 
on, antialias is aplicated with the background. Because the labels are
drawn one over the other, different colors are allocated unnecessary.

I suggest disable antialias when drawing to produce the outlined and
enable it only to draw the text. Image results are very similar and the number
of colors used are reduced.

Attachments (4)

tile_026.png (4.9 KB ) - added by pspencer@… 18 years ago.
this tile has incorrect blue color
tile_025.png (29.7 KB ) - added by pspencer@… 18 years ago.
correct tile color
tile_026.2.png (3.9 KB ) - added by pspencer@… 18 years ago.
incorrect tile, no dithering
tile_025.2.png (19.1 KB ) - added by pspencer@… 18 years ago.
correct color, no dithering

Download all attachments as: .zip

Change History (19)

comment:1 by sdlime, 18 years ago

I'm not sure that this is a bug that we can do anything about. If you want 
antialiasing at the edge of the outline then you need antialiasing on. GD could 
get smarter about color allocation I guess.

It's a known issue I guess and there are several ways around the problem:

  - turn of antialiasing of labels (which is off by default)
  - use a 24-bit output format

In addition there is work going on elsewhere to look at real-time 
quantatization from 24-bits to 8-bits. 

I'll run a test or two but I don't think this is going anywhere...

Steve

comment:2 by pspencer@…, 18 years ago

Cc: assefa@… pspencer@… added
Steve, I have successfully used the gd function to quantize 24bit pngs into 8bit pngs with no apparent lose 
of image quality using antialiased thick lines with casement styles and antialiased true type text ... loads 
of colours in there and there is no visible (to my poor eye) difference.  This has the additional advantage of 
fixing the colour problems with pixmap symbol colour corruption in 8bit mode.  I assume there is a 
performance hit but am not in a position to measure that.

I have been discussing this with Assefa.  Not sure how this would end up in MapServer but I would imagine 
it would be an option inside an outputformat object to apply the gdTrueColorToPalette function just 
before a save.

comment:3 by assefa, 18 years ago

Cc: warmerdam@… added
I think this could make it like a format option for gd/png24 and gd/jpeg (RGB).

 The function to be used is  gdImageCreatePaletteFromTrueColor(gdImagePtr im,
int ditherFlag, int colorsWanted) so we might want to give the dithering flag
and the number of colors as parameters.

 The change will ne done in function msSaveImageGDCtx in mapgd.c

 Frank : any input on this. Thanks.

  

comment:4 by fwarmerdam, 18 years ago

I think it would be quite confusing to use GD/JPEG and end up with a 
paletted image in some other format.   My suggestion is that we
start just applying it to PNG format since it is easy to understand
the concept of render in 24bit and switch at the last second to 8bit
before writing. 

For now this could just be declared as FORMATOPTION.  

eg. 
FORMATOPTION "DITHER=YES"

Then we can contemplate expanding this feature more widely. 

I do think that we will need to make some effort to explain how this
differs from enabling 24 to 8 bit quantization in the raster renderer
in the documentation so people don't get to confused. 

I'd be happy to incorporate the DITHER=YES FORMATOPTION in 4.9 if folks
would like.  

comment:5 by assefa, 18 years ago

I am ok with doing it on 24bit png to start with.

If we use the format option dither=yes, I am assuming the the low level gd
function call would always set the dither flag to true ? Is there any advantage
of setting that to false ? (the docs I have seen says soemthing like : ... "If
ditherFlag is set, the image will be dithered to approximate colors better, at
the expense of some obvious "speckling." ).  

I think clarifying this would help update the documentation too.

Frank, thanks also for proposition to integrte it. Yes please do so. 

comment:6 by sdlime, 18 years ago

I agree that PNG seems a place to start to try it out. It'll be interesting to 
see the performance hit. May be a *very* popular addition if it works well.

Perhaps the discussion should be moved back to the bug Assefa created for it. ;-
)

Steve

comment:7 by assefa, 18 years ago

oops. I though It was the same bug :) You are right, there is one opened for 
the gd24-8bis (Bug 1690).  I have updated the Bug 1690 to indicate that part 
of the comnversation is in this bug.

comment:8 by fwarmerdam, 18 years ago

Implemented quantize support per Bug 1690

Please test, and consider closing this bug if that new feature addresses
the problem. 

comment:9 by arovira@…, 18 years ago

Resolution: fixed
Status: newclosed
I think quantize support will solve this problem and is a very good feature 
for mapserver. I have used it in some particular implementations via Java 
mapscript + gdscript and give good results.

If somebody can't not use this feature and want antialiased font with 
outlines for readability he can tweak mapserver to disable antialias between 
background map and the outline line.

So the code in mapgd.c


    if(label->outlinecolor.pen >= 0) { /* handle the outline color */
      error = gdImageStringFT(img, bbox, ((label->antialias)?(label-
>outlinecolor.pen):-(label->outlinecolor.pen)), font, size, angle_radians, x, y-
1, string);
      if(error) {
        if( gdImageTrueColor(img) )
            gdImageAlphaBlending( img, oldAlphaBlending );
	msSetError(MS_TTFERR, error, "msDrawTextGD()");
        if(label->encoding != NULL) msFree(string);
	return(-1);
      }

      gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-
(label->outlinecolor.pen)), font, size, angle_radians, x, y+1, string);
      gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-
(label->outlinecolor.pen)), font, size, angle_radians, x+1, y, string);
      gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-
(label->outlinecolor.pen)), font, size, angle_radians, x-1, y, string);
      
      gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-
(label->outlinecolor.pen)), font, size, angle_radians, x-1, y-1, string);      
      gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-
(label->outlinecolor.pen)), font, size, angle_radians, x-1, y+1, string);
      gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-
(label->outlinecolor.pen)), font, size, angle_radians, x+1, y-1, string);
      gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-
(label->outlinecolor.pen)), font, size, angle_radians, x+1, y+1, string);
    }


will be

    if(label->outlinecolor.pen >= 0) { /* handle the outline color */
      error = gdImageStringFT(img, bbox, -(label->outlinecolor.pen), font, 
size, angle_radians, x, y-1, string);
      if(error) {
        if( gdImageTrueColor(img) )
            gdImageAlphaBlending( img, oldAlphaBlending );
	msSetError(MS_TTFERR, error, "msDrawTextGD()");
        if(label->encoding != NULL) msFree(string);
	return(-1);
      }

      gdImageStringFT(img, bbox, -(label->outlinecolor.pen),font, size, 
angle_radians, x, y+1, string);
      gdImageStringFT(img, bbox, -(label->outlinecolor.pen),font, size, 
angle_radians, x+1, y, string);
      gdImageStringFT(img, bbox, -(label->outlinecolor.pen),font, size, 
angle_radians, x-1, y, string);
      
      gdImageStringFT(img, bbox, -(label->outlinecolor.pen), font, size, 
angle_radians, x-1, y-1, string);      
      gdImageStringFT(img, bbox, -(label->outlinecolor.pen), font, size, 
angle_radians, x-1, y+1, string);
      gdImageStringFT(img, bbox, -(label->outlinecolor.pen), font, size, 
angle_radians, x+1, y-1, string);
      gdImageStringFT(img, bbox, -(label->outlinecolor.pen), font, size, 
angle_radians, x+1, y+1, string);
    }


Thanks,
Albert.



comment:10 by pspencer@…, 18 years ago

fors for me too :)  Thanks Frank/Steve/Assefa.

comment:11 by pspencer@…, 18 years ago

Resolution: fixed
Status: closedreopened
I spoke too soon.  Not sure if this is a solveable problem.  I've got a couple of cases where it is coming 
up with substantially different colours even though the colour table of the resulting image is not full.

Output format is:


OUTPUTFORMAT
  NAME dithered
  DRIVER "GD/PNG"
  EXTENSION "png"
  MIMETYPE "image/png"
  IMAGEMODE RGBA
  TRANSPARENT OFF
  FORMATOPTION "QUANTIZE_FORCE=ON"
  FORMATOPTION "QUANTIZE_DITHER=OFF"
  FORMATOPTION "QUANTIZE_COLORS=256"
END

I tried with dither on and off and will attach samples of both.

by pspencer@…, 18 years ago

Attachment: tile_026.png added

this tile has incorrect blue color

by pspencer@…, 18 years ago

Attachment: tile_025.png added

correct tile color

by pspencer@…, 18 years ago

Attachment: tile_026.2.png added

incorrect tile, no dithering

by pspencer@…, 18 years ago

Attachment: tile_025.2.png added

correct color, no dithering

comment:12 by fwarmerdam, 18 years ago

Paul, 

I have analysed your results, and I see you are indeed getting noticably 
different blues in different quantized images.  For instance (153,182,196)
vs. (156,190,220).  I have reviewed the algorithm description in 
gd-2.0.33/gd_topal.c and I think this is essentially inevitable.   You can
read the description in that source file, but basically changes in the data
can result in a large or smaller box being formed for the blue, and the
final color used is the "box center", not the exact color that falls in the
box.  

I think per-tile quantization is just inappropriate for ka-map.  What you
really need is a way of providing a color table and force mapserver to 
dither the 24bit data to that color table.  If you had a fixed color table 
you would get dependable mapping to final colors. 

Hmm, actually on reflection, I'm surprised the dithering doesn't mostly
fix this up.  

In any event, I don't believe this represents a problem in mapserver.  I
think it is inappropriateness of per-tile quantization in a tile based
viewer. 

comment:13 by pspencer@…, 18 years ago

What is strange is that the vast majority of the incorrect tile contains the blue.  Note also that the image 
that mapserver renders is actually 16 of those tiles and most are in the ocean.

What would be needed to provide a fixed palette and dither to that?  The fixed palette could easily be 
calculated from the colours allocated in the map file (and the colour tables of image symbols I guess).


comment:14 by fwarmerdam, 18 years ago

Paul,

At the low level version we would need to implement our own dithering
modules (or reuse the dithering from GDAL already used in the mapserver
raster rendering).  How to collect or specify the color table is not so
clear.  It is especially hard to collect a colortable automatically that
will address colors introduced by anti-aliasing or rasters. 

One approach would be to just use a fixed color cube.  In combination with
dithering this might give reasonable results.  The other would be to 
allow specifying a fixed colormap in the map file somehow. 


comment:15 by pspencer@…, 18 years ago

could the colour table be collected through colours defined in the map file when they are used?

Regarding colours used by antialiasing, for the most part quantization seems to do an outstanding job of 
this.  It just seems to be overly aggressive.  When I specify 256 colours, I would expect the output image 
to have 256 colours if the input image has more than 256 colours, or the exact colours if the input image 
has less than 256 colours.

My interpretation of your comment above is that this is not what is happening.  Am I correct?  If I have an 
input image with less than 256 distinct colours, I could end up with less colours in the output image than 
the input image?  And if I have more than 256 colours, I could end up with less than 256 as well?
Note: See TracTickets for help on using tickets.