commit 8b1e856b793f39f70fca4763c08ad568a2641f31
parent b2a5cbd896caf2321b9ef1dfe1368876bfaa6e2b
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date: Tue, 29 Oct 2024 12:08:59 +0800
icns.Probe: allow probing for icons
This allows the caller to list all the icons found, regardless of
support for the icon. This can help diagnose issues and clarify why an
icns file cannot be decoded.
Signed-off-by: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Diffstat:
| M | reader.go | | | 48 | +++++++++++++++++++++++++++++++++++------------- |
1 file changed, 35 insertions(+), 13 deletions(-)
diff --git a/reader.go b/reader.go
@@ -12,8 +12,8 @@ import (
var jpeg2000header = []byte{0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20}
// Decode finds the largest icon listed in the icns file and returns it,
-// ignoring all other sizes. The format returned will be whatever the icon data
-// is, typically jpeg or png.
+// ignoring all other sizes. The format returned will be PNG. JPEG 2000
+// icons are ignored due to lack of image decoding support.
func Decode(r io.Reader) (image.Image, error) {
icons, err := decode(r)
if err != nil {
@@ -22,23 +22,29 @@ func Decode(r io.Reader) (image.Image, error) {
sort.Slice(icons, func(ii, jj int) bool {
return icons[ii].OsType.Size > icons[jj].OsType.Size
})
- img, _, err := image.Decode(icons[0].r)
+ icon := icons[0]
+ img, _, err := image.Decode(icon.r)
if err != nil {
- return nil, fmt.Errorf("decoding largest image: %w", err)
+ return nil, fmt.Errorf("decoding largest image (icon %s %s): %w", icon.OsType, icon.ImageFormat, err)
}
return img, nil
}
-// DecodeAll extracts all icon resolutions present in the icns data.
+// DecodeAll extracts all icon resolutions present in the icns data that
+// contain PNG data. JPEG 2000 is ignored due to lack of image decoding
+// support.
func DecodeAll(r io.Reader) (images []image.Image, err error) {
icons, err := decode(r)
if err != nil {
return nil, err
}
for _, icon := range icons {
+ if icon.IconDescription.ImageFormat == ImageFormatJPEG2000 {
+ continue
+ }
img, _, err := image.Decode(icon.r)
if err != nil {
- return nil, fmt.Errorf("decoding %q icon: %w", icon.OsType, err)
+ return nil, fmt.Errorf("decoding icon %s %s: %w", icon.OsType, icon.ImageFormat, err)
}
images = append(images, img)
}
@@ -47,11 +53,24 @@ func DecodeAll(r io.Reader) (images []image.Image, err error) {
left = images[ii].Bounds().Size()
right = images[jj].Bounds().Size()
)
- return (left.X * left.Y) > (right.X * right.Y)
+ return (left.X + left.Y) > (right.X + right.Y)
})
return images, nil
}
+// Probe extracts descriptions of the icons in the icns.
+func Probe(r io.Reader) (desc []IconDescription, _ error) {
+ icons, err := decode(r)
+ if err != nil {
+ return nil, err
+ }
+ for _, icon := range icons {
+ desc = append(desc, icon.IconDescription)
+ }
+ return desc, nil
+}
+
+// decode identifies the icons in the icns (without decoding the image data).
func decode(r io.Reader) (icons []iconReader, err error) {
data, err := io.ReadAll(r)
if err != nil {
@@ -85,13 +104,16 @@ func decode(r io.Reader) (icons []iconReader, err error) {
iconData := data[read : read+dataSize-8]
read += dataSize - 8 // size includes header and size fields
if isOsType(string(next)) {
+ ir := iconReader{
+ IconDescription: IconDescription{
+ OsType: osTypeFromID(string(next)),
+ },
+ r: bytes.NewBuffer(iconData),
+ }
if bytes.Equal(iconData[:8], jpeg2000header) {
- continue // skipping JPEG2000
+ ir.ImageFormat = ImageFormatJPEG2000
}
- icons = append(icons, iconReader{
- OsType: osTypeFromID(string(next)),
- r: bytes.NewBuffer(iconData),
- })
+ icons = append(icons, ir)
}
}
if len(icons) == 0 {
@@ -101,7 +123,7 @@ func decode(r io.Reader) (icons []iconReader, err error) {
}
type iconReader struct {
- OsType
+ IconDescription
r io.Reader
}