1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2024-11-24 08:12:38 +02:00

ICO support

This commit is contained in:
DarthSim 2018-12-02 19:02:19 +06:00
parent 4913ed0e47
commit 4549089183
21 changed files with 819 additions and 3 deletions

12
Gopkg.lock generated
View File

@ -173,6 +173,14 @@
pruneopts = "UT"
revision = "ae77be60afb1dcacde03767a8c37337fad28ac14"
[[projects]]
digest = "1:d33e9f151ad66f61e320511ee3a2189178b78e2d1e93f098801b4ecf6154474a"
name = "github.com/mat/besticon"
packages = ["ico"]
pruneopts = "UT"
revision = "257c937cf48f690c78c900f895e9fc4027962ac2"
version = "v3.8.0"
[[projects]]
branch = "master"
digest = "1:97e35d2efabea52337e58f96e8b798fd461bf42d2519beb50f485029c6ad6aa5"
@ -314,9 +322,10 @@
version = "v0.18.0"
[[projects]]
digest = "1:72b36febaabad58e1864de2b43de05689ce27a2c9a582a61a25e71a31ba23d0b"
digest = "1:16d8d746fb42f8318537e77dbf22745c180db4f5b61b59a89e0fb8101bb3303e"
name = "golang.org/x/image"
packages = [
"bmp",
"riff",
"vp8",
"vp8l",
@ -494,6 +503,7 @@
"github.com/aws/aws-sdk-go/service/s3",
"github.com/bugsnag/bugsnag-go",
"github.com/honeybadger-io/honeybadger-go",
"github.com/mat/besticon/ico",
"github.com/matoous/go-nanoid",
"github.com/newrelic/go-agent",
"github.com/prometheus/client_golang/prometheus",

View File

@ -5,7 +5,8 @@ At the moment, imgproxy supports only the most popular Web image formats:
* PNG;
* JPEG;
* WebP;
* GIF.
* GIF;
* ICO.
## GIF support
@ -16,3 +17,7 @@ Since processing of animated GIFs is pretty heavy, only one frame is processed b
* `IMGPROXY_MAX_GIF_FRAMES`: the maximum of animated GIF frames to being processed. Default: `1`.
**Note:** imgproxy summarizes all GIF frames resolutions while checking source image resolution.
## ICO support
imgproxy supports ICO output only when using libvips 8.7.0+ compiled with ImageMagick support. Official imgproxy Docker image supports ICO out of the box.

View File

@ -16,6 +16,7 @@ import (
_ "image/jpeg"
_ "image/png"
_ "github.com/mat/besticon/ico"
_ "golang.org/x/image/webp"
)

28
ico_data.go Normal file
View File

@ -0,0 +1,28 @@
package main
import (
"bytes"
"image"
"image/draw"
_ "github.com/mat/besticon/ico"
)
func icoData(data []byte) (out []byte, width int, height int, err error) {
var ico image.Image
ico, _, err = image.Decode(bytes.NewReader(data))
if err != nil {
return
}
// Ensure that image is in RGBA format
rgba := image.NewRGBA(ico.Bounds())
draw.Draw(rgba, ico.Bounds(), ico, image.ZP, draw.Src)
width = rgba.Bounds().Dx()
height = rgba.Bounds().Dy()
out = rgba.Pix
return
}

View File

@ -3,5 +3,6 @@ enum types {
JPEG,
PNG,
WEBP,
GIF
GIF,
ICO
};

View File

@ -74,6 +74,9 @@ func initVips() {
vipsTypeSupportLoad[imageTypeGIF] = true
}
// we load ICO with github.com/mat/besticon/ico and send decoded data to vips
vipsTypeSupportLoad[imageTypeICO] = true
if int(C.vips_type_find_save_go(C.int(imageTypeJPEG))) != 0 {
vipsTypeSupportSave[imageTypeJPEG] = true
}
@ -86,6 +89,9 @@ func initVips() {
if int(C.vips_type_find_save_go(C.int(imageTypeGIF))) != 0 {
vipsTypeSupportSave[imageTypeGIF] = true
}
if int(C.vips_type_find_save_go(C.int(imageTypeICO))) != 0 {
vipsTypeSupportSave[imageTypeICO] = true
}
if conf.JpegProgressive {
cConf.JpegProgressive = C.int(1)
@ -606,6 +612,13 @@ func vipsLoadImage(data []byte, imgtype imageType, shrink int, allPages bool) (*
err = C.vips_webpload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.int(shrink), &img)
case imageTypeGIF:
err = C.vips_gifload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), pages, &img)
case imageTypeICO:
rawData, width, height, icoErr := icoData(data)
if icoErr != nil {
return nil, icoErr
}
img = C.vips_image_new_from_memory_copy(unsafe.Pointer(&rawData[0]), C.size_t(width*height*4), C.int(width), C.int(height), 4, C.VIPS_FORMAT_UCHAR)
}
if err != 0 {
return nil, vipsError()
@ -634,6 +647,8 @@ func vipsSaveImage(img *C.struct__VipsImage, imgtype imageType, quality int) ([]
err = C.vips_webpsave_go(img, &ptr, &imgsize, 1, C.int(quality))
case imageTypeGIF:
err = C.vips_gifsave_go(img, &ptr, &imgsize)
case imageTypeICO:
err = C.vips_icosave_go(img, &ptr, &imgsize)
}
if err != 0 {
return nil, vipsError()

View File

@ -28,6 +28,7 @@ const (
imageTypePNG = imageType(C.PNG)
imageTypeWEBP = imageType(C.WEBP)
imageTypeGIF = imageType(C.GIF)
imageTypeICO = imageType(C.ICO)
)
type processingHeaders struct {
@ -43,6 +44,7 @@ var imageTypes = map[string]imageType{
"png": imageTypePNG,
"webp": imageTypeWEBP,
"gif": imageTypeGIF,
"ico": imageTypeICO,
}
type gravityType int

View File

@ -25,6 +25,7 @@ var (
imageTypePNG: "image/png",
imageTypeWEBP: "image/webp",
imageTypeGIF: "image/gif",
imageTypeICO: "image/x-icon",
}
contentDispositions = map[imageType]string{
@ -32,6 +33,7 @@ var (
imageTypePNG: "inline; filename=\"image.png\"",
imageTypeWEBP: "inline; filename=\"image.webp\"",
imageTypeGIF: "inline; filename=\"image.gif\"",
imageTypeICO: "inline; filename=\"favicon.ico\"",
}
authHeaderMust []byte

21
vendor/github.com/mat/besticon/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-2018 Matthias Lüdtke, Hamburg - https://github.com/mat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

BIN
vendor/github.com/mat/besticon/ico/addthis.ico generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
vendor/github.com/mat/besticon/ico/besticon.ico generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
vendor/github.com/mat/besticon/ico/broken.ico generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 B

BIN
vendor/github.com/mat/besticon/ico/codeplex.ico generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
vendor/github.com/mat/besticon/ico/favicon.ico generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
vendor/github.com/mat/besticon/ico/github.ico generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

261
vendor/github.com/mat/besticon/ico/ico.go generated vendored Normal file
View File

@ -0,0 +1,261 @@
// Package ico registers image.Decode and DecodeConfig support
// for the icon (container) format.
package ico
import (
"bytes"
"encoding/binary"
"errors"
"image"
"io"
"io/ioutil"
"image/png"
"golang.org/x/image/bmp"
)
type icondir struct {
Reserved uint16
Type uint16
Count uint16
Entries []icondirEntry
}
type icondirEntry struct {
Width byte
Height byte
PaletteCount byte
Reserved byte
ColorPlanes uint16
BitsPerPixel uint16
Size uint32
Offset uint32
}
func (dir *icondir) FindBestIcon() *icondirEntry {
if len(dir.Entries) == 0 {
return nil
}
best := dir.Entries[0]
for _, e := range dir.Entries {
if (e.width() > best.width()) && (e.height() > best.height()) {
best = e
}
}
return &best
}
// ParseIco parses the icon and returns meta information for the icons as icondir.
func ParseIco(r io.Reader) (*icondir, error) {
dir := icondir{}
var err error
err = binary.Read(r, binary.LittleEndian, &dir.Reserved)
if err != nil {
return nil, err
}
err = binary.Read(r, binary.LittleEndian, &dir.Type)
if err != nil {
return nil, err
}
err = binary.Read(r, binary.LittleEndian, &dir.Count)
if err != nil {
return nil, err
}
for i := uint16(0); i < dir.Count; i++ {
entry := icondirEntry{}
e := parseIcondirEntry(r, &entry)
if e != nil {
return nil, e
}
dir.Entries = append(dir.Entries, entry)
}
return &dir, err
}
func parseIcondirEntry(r io.Reader, e *icondirEntry) error {
err := binary.Read(r, binary.LittleEndian, e)
if err != nil {
return err
}
return nil
}
type dibHeader struct {
dibHeaderSize uint32
width uint32
height uint32
}
func (e *icondirEntry) ColorCount() int {
if e.PaletteCount == 0 {
return 256
}
return int(e.PaletteCount)
}
func (e *icondirEntry) width() int {
if e.Width == 0 {
return 256
}
return int(e.Width)
}
func (e *icondirEntry) height() int {
if e.Height == 0 {
return 256
}
return int(e.Height)
}
// DecodeConfig returns just the dimensions of the largest image
// contained in the icon withou decoding the entire icon file.
func DecodeConfig(r io.Reader) (image.Config, error) {
dir, err := ParseIco(r)
if err != nil {
return image.Config{}, err
}
best := dir.FindBestIcon()
if best == nil {
return image.Config{}, errInvalid
}
return image.Config{Width: best.width(), Height: best.height()}, nil
}
// The bitmap header structure we read from an icondirEntry
type bitmapHeaderRead struct {
Size uint32
Width uint32
Height uint32
Planes uint16
BitCount uint16
Compression uint32
ImageSize uint32
XPixelsPerMeter uint32
YPixelsPerMeter uint32
ColorsUsed uint32
ColorsImportant uint32
}
// The bitmap header structure we need to generate for bmp.Decode()
type bitmapHeaderWrite struct {
sigBM [2]byte
fileSize uint32
resverved [2]uint16
pixOffset uint32
Size uint32
Width uint32
Height uint32
Planes uint16
BitCount uint16
Compression uint32
ImageSize uint32
XPixelsPerMeter uint32
YPixelsPerMeter uint32
ColorsUsed uint32
ColorsImportant uint32
}
var errInvalid = errors.New("ico: invalid ICO image")
// Decode returns the largest image contained in the icon
// which might be a bmp or png
func Decode(r io.Reader) (image.Image, error) {
icoBytes, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
r = bytes.NewReader(icoBytes)
dir, err := ParseIco(r)
if err != nil {
return nil, errInvalid
}
best := dir.FindBestIcon()
if best == nil {
return nil, errInvalid
}
return parseImage(best, icoBytes)
}
func parseImage(entry *icondirEntry, icoBytes []byte) (image.Image, error) {
r := bytes.NewReader(icoBytes)
r.Seek(int64(entry.Offset), 0)
// Try PNG first then BMP
img, err := png.Decode(r)
if err != nil {
return parseBMP(entry, icoBytes)
}
return img, nil
}
func parseBMP(entry *icondirEntry, icoBytes []byte) (image.Image, error) {
bmpBytes, err := makeFullBMPBytes(entry, icoBytes)
if err != nil {
return nil, err
}
return bmp.Decode(bmpBytes)
}
func makeFullBMPBytes(entry *icondirEntry, icoBytes []byte) (*bytes.Buffer, error) {
r := bytes.NewReader(icoBytes)
r.Seek(int64(entry.Offset), 0)
var err error
h := bitmapHeaderRead{}
err = binary.Read(r, binary.LittleEndian, &h)
if err != nil {
return nil, err
}
if h.Size != 40 || h.Planes != 1 {
return nil, errInvalid
}
var pixOffset uint32
if h.ColorsUsed == 0 && h.BitCount <= 8 {
pixOffset = 14 + 40 + 4*(1<<h.BitCount)
} else {
pixOffset = 14 + 40 + 4*h.ColorsUsed
}
writeHeader := &bitmapHeaderWrite{
sigBM: [2]byte{'B', 'M'},
fileSize: 14 + 40 + uint32(len(icoBytes)), // correct? important?
pixOffset: pixOffset,
Size: 40,
Width: uint32(h.Width),
Height: uint32(h.Height / 2),
Planes: h.Planes,
BitCount: h.BitCount,
Compression: h.Compression,
ColorsUsed: h.ColorsUsed,
ColorsImportant: h.ColorsImportant,
}
buf := new(bytes.Buffer)
if err = binary.Write(buf, binary.LittleEndian, writeHeader); err != nil {
return nil, err
}
io.CopyN(buf, r, int64(entry.Size))
return buf, nil
}
const icoHeader = "\x00\x00\x01\x00"
func init() {
image.RegisterFormat("ico", icoHeader, Decode, DecodeConfig)
}

BIN
vendor/github.com/mat/besticon/ico/wowhead.ico generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,92 @@
This Font Software is licensed under the SIL Open Font License,
Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font
creation efforts of academic and linguistic communities, and to
provide a free and open framework in which fonts may be shared and
improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply to
any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software
components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to,
deleting, or substituting -- in part or in whole -- any of the
components of the Original Version, by changing formats or by porting
the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed,
modify, redistribute, and sell modified and unmodified copies of the
Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in
Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the
corresponding Copyright Holder. This restriction only applies to the
primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created using
the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

199
vendor/golang.org/x/image/bmp/reader.go generated vendored Normal file
View File

@ -0,0 +1,199 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package bmp implements a BMP image decoder and encoder.
//
// The BMP specification is at http://www.digicamsoft.com/bmp/bmp.html.
package bmp // import "golang.org/x/image/bmp"
import (
"errors"
"image"
"image/color"
"io"
)
// ErrUnsupported means that the input BMP image uses a valid but unsupported
// feature.
var ErrUnsupported = errors.New("bmp: unsupported BMP image")
func readUint16(b []byte) uint16 {
return uint16(b[0]) | uint16(b[1])<<8
}
func readUint32(b []byte) uint32 {
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
}
// decodePaletted reads an 8 bit-per-pixel BMP image from r.
// If topDown is false, the image rows will be read bottom-up.
func decodePaletted(r io.Reader, c image.Config, topDown bool) (image.Image, error) {
paletted := image.NewPaletted(image.Rect(0, 0, c.Width, c.Height), c.ColorModel.(color.Palette))
if c.Width == 0 || c.Height == 0 {
return paletted, nil
}
var tmp [4]byte
y0, y1, yDelta := c.Height-1, -1, -1
if topDown {
y0, y1, yDelta = 0, c.Height, +1
}
for y := y0; y != y1; y += yDelta {
p := paletted.Pix[y*paletted.Stride : y*paletted.Stride+c.Width]
if _, err := io.ReadFull(r, p); err != nil {
return nil, err
}
// Each row is 4-byte aligned.
if c.Width%4 != 0 {
_, err := io.ReadFull(r, tmp[:4-c.Width%4])
if err != nil {
return nil, err
}
}
}
return paletted, nil
}
// decodeRGB reads a 24 bit-per-pixel BMP image from r.
// If topDown is false, the image rows will be read bottom-up.
func decodeRGB(r io.Reader, c image.Config, topDown bool) (image.Image, error) {
rgba := image.NewRGBA(image.Rect(0, 0, c.Width, c.Height))
if c.Width == 0 || c.Height == 0 {
return rgba, nil
}
// There are 3 bytes per pixel, and each row is 4-byte aligned.
b := make([]byte, (3*c.Width+3)&^3)
y0, y1, yDelta := c.Height-1, -1, -1
if topDown {
y0, y1, yDelta = 0, c.Height, +1
}
for y := y0; y != y1; y += yDelta {
if _, err := io.ReadFull(r, b); err != nil {
return nil, err
}
p := rgba.Pix[y*rgba.Stride : y*rgba.Stride+c.Width*4]
for i, j := 0, 0; i < len(p); i, j = i+4, j+3 {
// BMP images are stored in BGR order rather than RGB order.
p[i+0] = b[j+2]
p[i+1] = b[j+1]
p[i+2] = b[j+0]
p[i+3] = 0xFF
}
}
return rgba, nil
}
// decodeNRGBA reads a 32 bit-per-pixel BMP image from r.
// If topDown is false, the image rows will be read bottom-up.
func decodeNRGBA(r io.Reader, c image.Config, topDown bool) (image.Image, error) {
rgba := image.NewNRGBA(image.Rect(0, 0, c.Width, c.Height))
if c.Width == 0 || c.Height == 0 {
return rgba, nil
}
y0, y1, yDelta := c.Height-1, -1, -1
if topDown {
y0, y1, yDelta = 0, c.Height, +1
}
for y := y0; y != y1; y += yDelta {
p := rgba.Pix[y*rgba.Stride : y*rgba.Stride+c.Width*4]
if _, err := io.ReadFull(r, p); err != nil {
return nil, err
}
for i := 0; i < len(p); i += 4 {
// BMP images are stored in BGRA order rather than RGBA order.
p[i+0], p[i+2] = p[i+2], p[i+0]
}
}
return rgba, nil
}
// Decode reads a BMP image from r and returns it as an image.Image.
// Limitation: The file must be 8, 24 or 32 bits per pixel.
func Decode(r io.Reader) (image.Image, error) {
c, bpp, topDown, err := decodeConfig(r)
if err != nil {
return nil, err
}
switch bpp {
case 8:
return decodePaletted(r, c, topDown)
case 24:
return decodeRGB(r, c, topDown)
case 32:
return decodeNRGBA(r, c, topDown)
}
panic("unreachable")
}
// DecodeConfig returns the color model and dimensions of a BMP image without
// decoding the entire image.
// Limitation: The file must be 8, 24 or 32 bits per pixel.
func DecodeConfig(r io.Reader) (image.Config, error) {
config, _, _, err := decodeConfig(r)
return config, err
}
func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown bool, err error) {
// We only support those BMP images that are a BITMAPFILEHEADER
// immediately followed by a BITMAPINFOHEADER.
const (
fileHeaderLen = 14
infoHeaderLen = 40
)
var b [1024]byte
if _, err := io.ReadFull(r, b[:fileHeaderLen+infoHeaderLen]); err != nil {
return image.Config{}, 0, false, err
}
if string(b[:2]) != "BM" {
return image.Config{}, 0, false, errors.New("bmp: invalid format")
}
offset := readUint32(b[10:14])
if readUint32(b[14:18]) != infoHeaderLen {
return image.Config{}, 0, false, ErrUnsupported
}
width := int(int32(readUint32(b[18:22])))
height := int(int32(readUint32(b[22:26])))
if height < 0 {
height, topDown = -height, true
}
if width < 0 || height < 0 {
return image.Config{}, 0, false, ErrUnsupported
}
// We only support 1 plane, 8 or 24 bits per pixel and no compression.
planes, bpp, compression := readUint16(b[26:28]), readUint16(b[28:30]), readUint32(b[30:34])
if planes != 1 || compression != 0 {
return image.Config{}, 0, false, ErrUnsupported
}
switch bpp {
case 8:
if offset != fileHeaderLen+infoHeaderLen+256*4 {
return image.Config{}, 0, false, ErrUnsupported
}
_, err = io.ReadFull(r, b[:256*4])
if err != nil {
return image.Config{}, 0, false, err
}
pcm := make(color.Palette, 256)
for i := range pcm {
// BMP images are stored in BGR order rather than RGB order.
// Every 4th byte is padding.
pcm[i] = color.RGBA{b[4*i+2], b[4*i+1], b[4*i+0], 0xFF}
}
return image.Config{ColorModel: pcm, Width: width, Height: height}, 8, topDown, nil
case 24:
if offset != fileHeaderLen+infoHeaderLen {
return image.Config{}, 0, false, ErrUnsupported
}
return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 24, topDown, nil
case 32:
if offset != fileHeaderLen+infoHeaderLen {
return image.Config{}, 0, false, ErrUnsupported
}
return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 32, topDown, nil
}
return image.Config{}, 0, false, ErrUnsupported
}
func init() {
image.RegisterFormat("bmp", "BM????\x00\x00\x00\x00", Decode, DecodeConfig)
}

166
vendor/golang.org/x/image/bmp/writer.go generated vendored Normal file
View File

@ -0,0 +1,166 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package bmp
import (
"encoding/binary"
"errors"
"image"
"io"
)
type header struct {
sigBM [2]byte
fileSize uint32
resverved [2]uint16
pixOffset uint32
dibHeaderSize uint32
width uint32
height uint32
colorPlane uint16
bpp uint16
compression uint32
imageSize uint32
xPixelsPerMeter uint32
yPixelsPerMeter uint32
colorUse uint32
colorImportant uint32
}
func encodePaletted(w io.Writer, pix []uint8, dx, dy, stride, step int) error {
var padding []byte
if dx < step {
padding = make([]byte, step-dx)
}
for y := dy - 1; y >= 0; y-- {
min := y*stride + 0
max := y*stride + dx
if _, err := w.Write(pix[min:max]); err != nil {
return err
}
if padding != nil {
if _, err := w.Write(padding); err != nil {
return err
}
}
}
return nil
}
func encodeRGBA(w io.Writer, pix []uint8, dx, dy, stride, step int) error {
buf := make([]byte, step)
for y := dy - 1; y >= 0; y-- {
min := y*stride + 0
max := y*stride + dx*4
off := 0
for i := min; i < max; i += 4 {
buf[off+2] = pix[i+0]
buf[off+1] = pix[i+1]
buf[off+0] = pix[i+2]
off += 3
}
if _, err := w.Write(buf); err != nil {
return err
}
}
return nil
}
func encode(w io.Writer, m image.Image, step int) error {
b := m.Bounds()
buf := make([]byte, step)
for y := b.Max.Y - 1; y >= b.Min.Y; y-- {
off := 0
for x := b.Min.X; x < b.Max.X; x++ {
r, g, b, _ := m.At(x, y).RGBA()
buf[off+2] = byte(r >> 8)
buf[off+1] = byte(g >> 8)
buf[off+0] = byte(b >> 8)
off += 3
}
if _, err := w.Write(buf); err != nil {
return err
}
}
return nil
}
// Encode writes the image m to w in BMP format.
func Encode(w io.Writer, m image.Image) error {
d := m.Bounds().Size()
if d.X < 0 || d.Y < 0 {
return errors.New("bmp: negative bounds")
}
h := &header{
sigBM: [2]byte{'B', 'M'},
fileSize: 14 + 40,
pixOffset: 14 + 40,
dibHeaderSize: 40,
width: uint32(d.X),
height: uint32(d.Y),
colorPlane: 1,
}
var step int
var palette []byte
switch m := m.(type) {
case *image.Gray:
step = (d.X + 3) &^ 3
palette = make([]byte, 1024)
for i := 0; i < 256; i++ {
palette[i*4+0] = uint8(i)
palette[i*4+1] = uint8(i)
palette[i*4+2] = uint8(i)
palette[i*4+3] = 0xFF
}
h.imageSize = uint32(d.Y * step)
h.fileSize += uint32(len(palette)) + h.imageSize
h.pixOffset += uint32(len(palette))
h.bpp = 8
case *image.Paletted:
step = (d.X + 3) &^ 3
palette = make([]byte, 1024)
for i := 0; i < len(m.Palette) && i < 256; i++ {
r, g, b, _ := m.Palette[i].RGBA()
palette[i*4+0] = uint8(b >> 8)
palette[i*4+1] = uint8(g >> 8)
palette[i*4+2] = uint8(r >> 8)
palette[i*4+3] = 0xFF
}
h.imageSize = uint32(d.Y * step)
h.fileSize += uint32(len(palette)) + h.imageSize
h.pixOffset += uint32(len(palette))
h.bpp = 8
default:
step = (3*d.X + 3) &^ 3
h.imageSize = uint32(d.Y * step)
h.fileSize += h.imageSize
h.bpp = 24
}
if err := binary.Write(w, binary.LittleEndian, h); err != nil {
return err
}
if palette != nil {
if err := binary.Write(w, binary.LittleEndian, palette); err != nil {
return err
}
}
if d.X == 0 || d.Y == 0 {
return nil
}
switch m := m.(type) {
case *image.Gray:
return encodePaletted(w, m.Pix, d.X, d.Y, m.Stride, step)
case *image.Paletted:
return encodePaletted(w, m.Pix, d.X, d.Y, m.Stride, step)
case *image.RGBA:
return encodeRGBA(w, m.Pix, d.X, d.Y, m.Stride, step)
}
return encode(w, m, step)
}

13
vips.h
View File

@ -69,6 +69,9 @@ vips_type_find_save_go(int imgtype) {
if (imgtype == GIF) {
return vips_type_find("VipsOperation", "magicksave_buffer");
}
if (imgtype == ICO) {
return vips_type_find("VipsOperation", "magicksave_buffer");
}
return 0;
}
@ -287,6 +290,16 @@ vips_gifsave_go(VipsImage *in, void **buf, size_t *len) {
#endif
}
int
vips_icosave_go(VipsImage *in, void **buf, size_t *len) {
#if VIPS_SUPPORT_MAGICK
return vips_magicksave_buffer(in, buf, len, "format", "ico", NULL);
#else
vips_error("vips_icosave_go", "Saving ICO is not supported");
return 1;
#endif
}
void
vips_cleanup() {
vips_thread_shutdown();