From 784dacec868d6befa2cfd68db03306de24095654 Mon Sep 17 00:00:00 2001 From: kentquirk Date: Mon, 7 Mar 2016 14:46:05 -0500 Subject: Support reading and/or setting image dpi Adds the ability to support reading dpi from PNG images, and setting dpi on any image directly; this allows images to be displayed at the designed size. --- def.go | 16 ++++++-- fpdf.go | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 130 insertions(+), 28 deletions(-) diff --git a/def.go b/def.go index 4c99733..d3eaca0 100644 --- a/def.go +++ b/def.go @@ -71,6 +71,7 @@ type ImageInfoType struct { dp string trns []int scale float64 // document scaling factor + dpi float64 } // PointConvert returns the value of pt, expressed in points (1/72 inch), as a @@ -97,17 +98,26 @@ func (f *Fpdf) UnitToPointConvert(u float64) (pt float64) { // Extent returns the width and height of the image in the units of the Fpdf // object. func (info *ImageInfoType) Extent() (wd, ht float64) { - return info.w / info.scale, info.h / info.scale + return info.Width(), info.Height() } // Width returns the width of the image in the units of the Fpdf object. func (info *ImageInfoType) Width() float64 { - return info.w / info.scale + return info.w / (info.scale * info.dpi / 72) } // Height returns the height of the image in the units of the Fpdf object. func (info *ImageInfoType) Height() float64 { - return info.h / info.scale + return info.h / (info.scale * info.dpi / 72) +} + +// Dpi sets the dots per inch for an image +// PNG images MAY have their dpi set automatically, if the image +// specifies it. DPI information is not currently available +// automatically for JPG and GIF images, so if it's important to +// you, you can set it here. It defaults to 72 dpi. +func (info *ImageInfoType) SetDpi(dpi float64) { + info.dpi = dpi } type fontFileType struct { diff --git a/fpdf.go b/fpdf.go index bc23674..4581cda 100644 --- a/fpdf.go +++ b/fpdf.go @@ -2250,6 +2250,16 @@ func (f *Fpdf) imageOut(info *ImageInfoType, x, y, w, h float64, flow bool, link w = -96 h = -96 } + if w == -1 { + // Set image width to whatever value for dpi we read + // from the image or that was set manually + w = -info.dpi + } + if h == -1 { + // Set image height to whatever value for dpi we read + // from the image or that was set manually + h = -info.dpi + } if w < 0 { w = -info.w * 72.0 / w / f.k } @@ -2287,11 +2297,30 @@ func (f *Fpdf) imageOut(info *ImageInfoType, x, y, w, h float64, flow bool, link } } +// Image puts a JPEG, PNG or GIF image in the current page. +// +// Deprecated in favor of ImageOptions -- see that function for +// details on the behavior of arguments +func (f *Fpdf) Image(imageNameStr string, x, y, w, h float64, flow bool, tp string, link int, linkStr string) { + options := ImageOptions{ + ReadDpi: false, + ImageType: tp, + } + f.ImageOptions(imageNameStr, x, y, w, h, flow, options, link, linkStr) +} + // Image puts a JPEG, PNG or GIF image in the current page. The size it will // take on the page can be specified in different ways. If both w and h are 0, // the image is rendered at 96 dpi. If either w or h is zero, it will be // calculated from the other dimension so that the aspect ratio is maintained. -// If w and h are negative, their absolute values indicate their dpi extents. +// If w and/or h are -1, the dpi for that dimension will be read from +// the ImageInfoType object. PNG files can contain dpi information, and if +// present, this information will be populated in the ImageInfoType object +// and used in Width, Height, and Extent calculations. Otherwise, the +// SetDpi function can be used to change the dpi from the default of 72. +// +// If w and h are any other negative value, their absolute values +// indicate their dpi extents. // // Supported JPEG formats are 24 bit, 32 bit and gray scale. Supported PNG // formats are 24 bit, indexed color, and 8 bit indexed gray scale. If a GIF @@ -2313,18 +2342,14 @@ func (f *Fpdf) imageOut(info *ImageInfoType, x, y, w, h float64, flow bool, link // If flow is true, the current y value is advanced after placing the image and // a page break may be made if necessary. // -// tp specifies the image format. Possible values are (case insensitive): -// "JPG", "JPEG", "PNG" and "GIF". If not specified, the type is inferred from -// the file extension. -// // If link refers to an internal page anchor (that is, it is non-zero; see // AddLink()), the image will be a clickable internal link. Otherwise, if // linkStr specifies a URL, the image will be a clickable external link. -func (f *Fpdf) Image(imageNameStr string, x, y, w, h float64, flow bool, tp string, link int, linkStr string) { +func (f *Fpdf) ImageOptions(imageNameStr string, x, y, w, h float64, flow bool, options ImageOptions, link int, linkStr string) { if f.err != nil { return } - info := f.RegisterImage(imageNameStr, tp) + info := f.RegisterImageOptions(imageNameStr, options) if f.err != nil { return } @@ -2333,12 +2358,41 @@ func (f *Fpdf) Image(imageNameStr string, x, y, w, h float64, flow bool, tp stri } // RegisterImageReader registers an image, reading it from Reader r, adding it +// to the PDF file but not adding it to the page. +// +// This function is now deprecated in favor of RegisterImageOptionsReader +func (f *Fpdf) RegisterImageReader(imgName, tp string, r io.Reader) (info *ImageInfoType) { + options := ImageOptions{ + ReadDpi: false, + ImageType: tp, + } + return f.RegisterImageOptionsReader(imgName, options, r) +} + +// ImageOptions provides a place to hang any options we want to use while +// parsing an image. +// +// ImageType's possible values are (case insensitive): +// "JPG", "JPEG", "PNG" and "GIF". If empty, the type is inferred from +// the file extension. +// +// ReadDpi defines whether to attempt to automatically read the image +// dpi information from the image file. Normally, this should be set +// to true (understanding that not all images will have this info +// available). However, for backwards compatibility with previous +// versions of the API, it defaults to false. +type ImageOptions struct { + ImageType string + ReadDpi bool +} + +// RegisterImageOptionsReader registers an image, reading it from Reader r, adding it // to the PDF file but not adding it to the page. Use Image() with the same // name to add the image to the page. Note that tp should be specified in this // case. // -// See Image() for restrictions on the image and the "tp" parameters. -func (f *Fpdf) RegisterImageReader(imgName, tp string, r io.Reader) (info *ImageInfoType) { +// See Image() for restrictions on the image and the options parameters. +func (f *Fpdf) RegisterImageOptionsReader(imgName string, options ImageOptions, r io.Reader) (info *ImageInfoType) { // Thanks, Ivan Daniluk, for generalizing this code to use the Reader interface. if f.err != nil { return @@ -2349,23 +2403,23 @@ func (f *Fpdf) RegisterImageReader(imgName, tp string, r io.Reader) (info *Image } // First use of this image, get info - if tp == "" { + if options.ImageType == "" { f.err = fmt.Errorf("image type should be specified if reading from custom reader") return } - tp = strings.ToLower(tp) - if tp == "jpeg" { - tp = "jpg" + options.ImageType = strings.ToLower(options.ImageType) + if options.ImageType == "jpeg" { + options.ImageType = "jpg" } - switch tp { + switch options.ImageType { case "jpg": info = f.parsejpg(r) case "png": - info = f.parsepng(r) + info = f.parsepng(r, options.ReadDpi) case "gif": info = f.parsegif(r) default: - f.err = fmt.Errorf("unsupported image type: %s", tp) + f.err = fmt.Errorf("unsupported image type: %s", options.ImageType) } if f.err != nil { return @@ -2376,12 +2430,27 @@ func (f *Fpdf) RegisterImageReader(imgName, tp string, r io.Reader) (info *Image return } +// RegisterImage registers an image, adding it to the PDF file but not adding +// it to the page. Use Image() with the same filename to add the image to the +// page. Note that Image() calls this function, so this function is only +// necessary if you need information about the image before placing it. +// +// This function is now deprecated in favor of RegisterImageOptions. +// See Image() for restrictions on the image and the "tp" parameters. +func (f *Fpdf) RegisterImage(fileStr, tp string) (info *ImageInfoType) { + options := ImageOptions{ + ReadDpi: false, + ImageType: tp, + } + return f.RegisterImageOptions(fileStr, options) +} + // RegisterImage registers an image, adding it to the PDF file but not adding // it to the page. Use Image() with the same filename to add the image to the // page. Note that Image() calls this function, so this function is only // necessary if you need information about the image before placing it. See // Image() for restrictions on the image and the "tp" parameters. -func (f *Fpdf) RegisterImage(fileStr, tp string) (info *ImageInfoType) { +func (f *Fpdf) RegisterImageOptions(fileStr string, options ImageOptions) (info *ImageInfoType) { info, ok := f.images[fileStr] if ok { return @@ -2395,16 +2464,16 @@ func (f *Fpdf) RegisterImage(fileStr, tp string) (info *ImageInfoType) { defer file.Close() // First use of this image, get info - if tp == "" { + if options.ImageType == "" { pos := strings.LastIndex(fileStr, ".") if pos < 0 { f.err = fmt.Errorf("image file has no extension and no type was specified: %s", fileStr) return } - tp = fileStr[pos+1:] + options.ImageType = fileStr[pos+1:] } - return f.RegisterImageReader(fileStr, tp, file) + return f.RegisterImageOptionsReader(fileStr, options, file) } // GetImageInfo returns information about the registered image specified by @@ -2682,7 +2751,8 @@ func be16(buf []byte) int { } func (f *Fpdf) newImageInfo() *ImageInfoType { - return &ImageInfoType{scale: f.k} + // default dpi to 72 unless told otherwise + return &ImageInfoType{scale: f.k, dpi: 72} } // Extract info from io.Reader with JPEG data @@ -2722,13 +2792,13 @@ func (f *Fpdf) parsejpg(r io.Reader) (info *ImageInfoType) { } // Extract info from a PNG data -func (f *Fpdf) parsepng(r io.Reader) (info *ImageInfoType) { +func (f *Fpdf) parsepng(r io.Reader, readdpi bool) (info *ImageInfoType) { buf, err := bufferFromReader(r) if err != nil { f.err = err return } - return f.parsepngstream(buf) + return f.parsepngstream(buf, readdpi) } func (f *Fpdf) readBeInt32(buf *bytes.Buffer) (val int32) { @@ -2747,7 +2817,7 @@ func (f *Fpdf) readByte(buf *bytes.Buffer) (val byte) { return } -func (f *Fpdf) parsepngstream(buf *bytes.Buffer) (info *ImageInfoType) { +func (f *Fpdf) parsepngstream(buf *bytes.Buffer, readdpi bool) (info *ImageInfoType) { info = f.newImageInfo() // Check signature if string(buf.Next(8)) != "\x89PNG\x0d\x0a\x1a\x0a" { @@ -2834,6 +2904,28 @@ func (f *Fpdf) parsepngstream(buf *bytes.Buffer) (info *ImageInfoType) { case "IEND": // dbg("IEND") loop = false + case "pHYs": + // dbg("pHYs") + // png files theoretically support different x/y dpi + // but we ignore files like this + // but if they're the same then we can stamp our info + // object with it + x := int(f.readBeInt32(buf)) + y := int(f.readBeInt32(buf)) + units := buf.Next(1)[0] + fmt.Printf("got a pHYs block, x=%d, y=%d, u=%d, readdpi=%t\n", + x, y, int(units), readdpi) + // only modify the info block if the user wants us to + if x == y && readdpi { + switch units { + // if units is 1 then measurement is px/meter + case 1: + info.dpi = float64(x) / 39.3701 // inches per meter + default: + info.dpi = float64(x) + } + } + _ = buf.Next(4) default: // dbg("default") _ = buf.Next(n + 4) @@ -2927,7 +3019,7 @@ func (f *Fpdf) parsegif(r io.Reader) (info *ImageInfoType) { f.err = err return } - return f.parsepngstream(pngBuf) + return f.parsepngstream(pngBuf, false) } // Begin a new object -- cgit v1.2.1-24-ge1ad From 84296ad0de25bc27403a0566119569152faa6d9f Mon Sep 17 00:00:00 2001 From: Kurt Date: Mon, 7 Mar 2016 19:29:25 -0500 Subject: Satisfy golint with small changes to documentation. Remove diagnostic print statement to allow tests to succeed. --- def.go | 9 ++++----- fpdf.go | 26 +++++++++++++------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/def.go b/def.go index d3eaca0..f5eed3a 100644 --- a/def.go +++ b/def.go @@ -111,11 +111,10 @@ func (info *ImageInfoType) Height() float64 { return info.h / (info.scale * info.dpi / 72) } -// Dpi sets the dots per inch for an image -// PNG images MAY have their dpi set automatically, if the image -// specifies it. DPI information is not currently available -// automatically for JPG and GIF images, so if it's important to -// you, you can set it here. It defaults to 72 dpi. +// SetDpi sets the dots per inch for an image. PNG images MAY have their dpi +// set automatically, if the image specifies it. DPI information is not +// currently available automatically for JPG and GIF images, so if it's +// important to you, you can set it here. It defaults to 72 dpi. func (info *ImageInfoType) SetDpi(dpi float64) { info.dpi = dpi } diff --git a/fpdf.go b/fpdf.go index 4581cda..952556a 100644 --- a/fpdf.go +++ b/fpdf.go @@ -2309,15 +2309,15 @@ func (f *Fpdf) Image(imageNameStr string, x, y, w, h float64, flow bool, tp stri f.ImageOptions(imageNameStr, x, y, w, h, flow, options, link, linkStr) } -// Image puts a JPEG, PNG or GIF image in the current page. The size it will -// take on the page can be specified in different ways. If both w and h are 0, -// the image is rendered at 96 dpi. If either w or h is zero, it will be +// ImageOptions puts a JPEG, PNG or GIF image in the current page. The size it +// will take on the page can be specified in different ways. If both w and h +// are 0, the image is rendered at 96 dpi. If either w or h is zero, it will be // calculated from the other dimension so that the aspect ratio is maintained. -// If w and/or h are -1, the dpi for that dimension will be read from -// the ImageInfoType object. PNG files can contain dpi information, and if -// present, this information will be populated in the ImageInfoType object -// and used in Width, Height, and Extent calculations. Otherwise, the -// SetDpi function can be used to change the dpi from the default of 72. +// If w and/or h are -1, the dpi for that dimension will be read from the +// ImageInfoType object. PNG files can contain dpi information, and if present, +// this information will be populated in the ImageInfoType object and used in +// Width, Height, and Extent calculations. Otherwise, the SetDpi function can +// be used to change the dpi from the default of 72. // // If w and h are any other negative value, their absolute values // indicate their dpi extents. @@ -2445,9 +2445,9 @@ func (f *Fpdf) RegisterImage(fileStr, tp string) (info *ImageInfoType) { return f.RegisterImageOptions(fileStr, options) } -// RegisterImage registers an image, adding it to the PDF file but not adding -// it to the page. Use Image() with the same filename to add the image to the -// page. Note that Image() calls this function, so this function is only +// RegisterImageOptions registers an image, adding it to the PDF file but not +// adding it to the page. Use Image() with the same filename to add the image +// to the page. Note that Image() calls this function, so this function is only // necessary if you need information about the image before placing it. See // Image() for restrictions on the image and the "tp" parameters. func (f *Fpdf) RegisterImageOptions(fileStr string, options ImageOptions) (info *ImageInfoType) { @@ -2913,8 +2913,8 @@ func (f *Fpdf) parsepngstream(buf *bytes.Buffer, readdpi bool) (info *ImageInfoT x := int(f.readBeInt32(buf)) y := int(f.readBeInt32(buf)) units := buf.Next(1)[0] - fmt.Printf("got a pHYs block, x=%d, y=%d, u=%d, readdpi=%t\n", - x, y, int(units), readdpi) + // fmt.Printf("got a pHYs block, x=%d, y=%d, u=%d, readdpi=%t\n", + // x, y, int(units), readdpi) // only modify the info block if the user wants us to if x == y && readdpi { switch units { -- cgit v1.2.1-24-ge1ad From 2c568dbb6ccf419450c6f64a782b7e83875f9c2f Mon Sep 17 00:00:00 2001 From: Kurt Date: Wed, 9 Mar 2016 09:49:26 -0500 Subject: Acknowledge Kent Quirk's image resolution support --- README.md | 6 ++++-- doc.go | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fdcbac4..b70b852 100644 --- a/README.md +++ b/README.md @@ -199,8 +199,10 @@ package presentation and tests. Templating is adapted by Marcus Downing from the FPDF_Tpl library created by Jan Slabon and Setasign. Jelmer Snoeck contributed packages that generate a variety of barcodes and help with registering images on the web. Additionally, he augmented the basic HTML -functionality with aligned text. Bruno Michel has provided valuable assistance -with the code. +functionality with aligned text. Kent Quirk implemented backwards-compatible +support for reading DPI from images that support it, and for setting DPI +manually and then having it properly taken into account when calculating image +size. Bruno Michel has provided valuable assistance with the code. ##Roadmap diff --git a/doc.go b/doc.go index 15bf40e..57bc539 100644 --- a/doc.go +++ b/doc.go @@ -217,8 +217,10 @@ package presentation and tests. Templating is adapted by Marcus Downing from the FPDF_Tpl library created by Jan Slabon and Setasign. Jelmer Snoeck contributed packages that generate a variety of barcodes and help with registering images on the web. Additionally, he augmented the basic HTML -functionality with aligned text. Bruno Michel has provided valuable assistance -with the code. +functionality with aligned text. Kent Quirk implemented backwards-compatible +support for reading DPI from images that support it, and for setting DPI +manually and then having it properly taken into account when calculating image +size. Bruno Michel has provided valuable assistance with the code. Roadmap -- cgit v1.2.1-24-ge1ad