add gbk encoder and decoder
|
@ -0,0 +1,10 @@
|
|||
# Treat all files in this repo as binary, with no git magic updating
|
||||
# line endings. Windows users contributing to Go will need to use a
|
||||
# modern version of git and editors capable of LF line endings.
|
||||
#
|
||||
# We'll prevent accidental CRLF line endings from entering the repo
|
||||
# via the git-review gofmt checks.
|
||||
#
|
||||
# See golang.org/issue/9281
|
||||
|
||||
* -text
|
|
@ -0,0 +1,2 @@
|
|||
# Add no patterns to .hgignore except for files generated by the build.
|
||||
last-change
|
|
@ -0,0 +1,3 @@
|
|||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
|
@ -0,0 +1,31 @@
|
|||
# Contributing to Go
|
||||
|
||||
Go is an open source project.
|
||||
|
||||
It is the work of hundreds of contributors. We appreciate your help!
|
||||
|
||||
|
||||
## Filing issues
|
||||
|
||||
When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
|
||||
|
||||
1. What version of Go are you using (`go version`)?
|
||||
2. What operating system and processor architecture are you using?
|
||||
3. What did you do?
|
||||
4. What did you expect to see?
|
||||
5. What did you see instead?
|
||||
|
||||
General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
|
||||
The gophers there will answer or ask you to file an issue if you've tripped over a bug.
|
||||
|
||||
## Contributing code
|
||||
|
||||
Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
|
||||
before sending patches.
|
||||
|
||||
**We do not accept GitHub pull requests**
|
||||
(we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review).
|
||||
|
||||
Unless otherwise noted, the Go source files are distributed under
|
||||
the BSD-style license found in the LICENSE file.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,22 @@
|
|||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
|
@ -0,0 +1,3 @@
|
|||
This repository holds supplementary Go image libraries.
|
||||
|
||||
To submit changes to this repository, see http://golang.org/doc/contribute.html.
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright 2012 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 (
|
||||
"fmt"
|
||||
"image"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
_ "image/png"
|
||||
)
|
||||
|
||||
const testdataDir = "../testdata/"
|
||||
|
||||
func compare(t *testing.T, img0, img1 image.Image) error {
|
||||
b := img1.Bounds()
|
||||
if !b.Eq(img0.Bounds()) {
|
||||
return fmt.Errorf("wrong image size: want %s, got %s", img0.Bounds(), b)
|
||||
}
|
||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||
for x := b.Min.X; x < b.Max.X; x++ {
|
||||
c0 := img0.At(x, y)
|
||||
c1 := img1.At(x, y)
|
||||
r0, g0, b0, a0 := c0.RGBA()
|
||||
r1, g1, b1, a1 := c1.RGBA()
|
||||
if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 {
|
||||
return fmt.Errorf("pixel at (%d, %d) has wrong color: want %v, got %v", x, y, c0, c1)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestDecode tests that decoding a PNG image and a BMP image result in the
|
||||
// same pixel data.
|
||||
func TestDecode(t *testing.T) {
|
||||
testCases := []string{
|
||||
"video-001",
|
||||
"yellow_rose-small",
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
f0, err := os.Open(testdataDir + tc + ".png")
|
||||
if err != nil {
|
||||
t.Errorf("%s: Open PNG: %v", tc, err)
|
||||
continue
|
||||
}
|
||||
defer f0.Close()
|
||||
img0, _, err := image.Decode(f0)
|
||||
if err != nil {
|
||||
t.Errorf("%s: Decode PNG: %v", tc, err)
|
||||
continue
|
||||
}
|
||||
|
||||
f1, err := os.Open(testdataDir + tc + ".bmp")
|
||||
if err != nil {
|
||||
t.Errorf("%s: Open BMP: %v", tc, err)
|
||||
continue
|
||||
}
|
||||
defer f1.Close()
|
||||
img1, _, err := image.Decode(f1)
|
||||
if err != nil {
|
||||
t.Errorf("%s: Decode BMP: %v", tc, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := compare(t, img0, img1); err != nil {
|
||||
t.Errorf("%s: %v", tc, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
// 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func openImage(filename string) (image.Image, error) {
|
||||
f, err := os.Open(testdataDir + filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return Decode(f)
|
||||
}
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
img0, err := openImage("video-001.bmp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
err = Encode(buf, img0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
img1, err := Decode(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
compare(t, img0, img1)
|
||||
}
|
||||
|
||||
// TestZeroWidthVeryLargeHeight tests that encoding and decoding a degenerate
|
||||
// image with zero width but over one billion pixels in height is faster than
|
||||
// naively calling an io.Reader or io.Writer method once per row.
|
||||
func TestZeroWidthVeryLargeHeight(t *testing.T) {
|
||||
c := make(chan error, 1)
|
||||
go func() {
|
||||
b := image.Rect(0, 0, 0, 0x3fffffff)
|
||||
var buf bytes.Buffer
|
||||
if err := Encode(&buf, image.NewRGBA(b)); err != nil {
|
||||
c <- err
|
||||
return
|
||||
}
|
||||
m, err := Decode(&buf)
|
||||
if err != nil {
|
||||
c <- err
|
||||
return
|
||||
}
|
||||
if got := m.Bounds(); got != b {
|
||||
c <- fmt.Errorf("bounds: got %v, want %v", got, b)
|
||||
return
|
||||
}
|
||||
c <- nil
|
||||
}()
|
||||
select {
|
||||
case err := <-c:
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatalf("timed out")
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkEncode benchmarks the encoding of an image.
|
||||
func BenchmarkEncode(b *testing.B) {
|
||||
img, err := openImage("video-001.bmp")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
s := img.Bounds().Size()
|
||||
b.SetBytes(int64(s.X * s.Y * 4))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
Encode(ioutil.Discard, img)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
// Copyright 2014 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.
|
||||
|
||||
// +build ignore
|
||||
//
|
||||
// This build tag means that "go install golang.org/x/image/..." doesn't
|
||||
// install this manual test. Use "go run main.go" to explicitly run it.
|
||||
|
||||
// Program webp-manual-test checks that the Go WEBP library's decodings match
|
||||
// the C WEBP library's.
|
||||
package main // import "golang.org/x/image/cmd/webp-manual-test"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"image"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/image/webp"
|
||||
"golang.org/x/image/webp/nycbcra"
|
||||
)
|
||||
|
||||
var (
|
||||
dwebp = flag.String("dwebp", "/usr/bin/dwebp", "path to the dwebp program "+
|
||||
"installed from https://developers.google.com/speed/webp/download")
|
||||
testdata = flag.String("testdata", "", "path to the libwebp-test-data directory "+
|
||||
"checked out from https://chromium.googlesource.com/webm/libwebp-test-data")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if *dwebp == "" {
|
||||
flag.Usage()
|
||||
log.Fatal("dwebp flag was not specified")
|
||||
}
|
||||
if _, err := os.Stat(*dwebp); err != nil {
|
||||
flag.Usage()
|
||||
log.Fatalf("could not find dwebp program at %q", *dwebp)
|
||||
}
|
||||
if *testdata == "" {
|
||||
flag.Usage()
|
||||
log.Fatal("testdata flag was not specified")
|
||||
}
|
||||
|
||||
f, err := os.Open(*testdata)
|
||||
if err != nil {
|
||||
log.Fatalf("Open: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
names, err := f.Readdirnames(-1)
|
||||
if err != nil {
|
||||
log.Fatalf("Readdirnames: %v", err)
|
||||
}
|
||||
sort.Strings(names)
|
||||
|
||||
nFail, nPass := 0, 0
|
||||
for _, name := range names {
|
||||
if !strings.HasSuffix(name, "webp") {
|
||||
continue
|
||||
}
|
||||
if err := test(name); err != nil {
|
||||
fmt.Printf("FAIL\t%s\t%v\n", name, err)
|
||||
nFail++
|
||||
} else {
|
||||
fmt.Printf("PASS\t%s\n", name)
|
||||
nPass++
|
||||
}
|
||||
}
|
||||
fmt.Printf("%d PASS, %d FAIL, %d TOTAL\n", nPass, nFail, nPass+nFail)
|
||||
if nFail != 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// test tests a single WEBP image.
|
||||
func test(name string) error {
|
||||
filename := filepath.Join(*testdata, name)
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Open: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
gotImage, err := webp.Decode(f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Decode: %v", err)
|
||||
}
|
||||
format, encode := "-pgm", encodePGM
|
||||
if _, lossless := gotImage.(*image.NRGBA); lossless {
|
||||
format, encode = "-pam", encodePAM
|
||||
}
|
||||
got, err := encode(gotImage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode: %v", err)
|
||||
}
|
||||
|
||||
stdout := new(bytes.Buffer)
|
||||
stderr := new(bytes.Buffer)
|
||||
c := exec.Command(*dwebp, filename, format, "-o", "/dev/stdout")
|
||||
c.Stdout = stdout
|
||||
c.Stderr = stderr
|
||||
if err := c.Run(); err != nil {
|
||||
os.Stderr.Write(stderr.Bytes())
|
||||
return fmt.Errorf("executing dwebp: %v", err)
|
||||
}
|
||||
want := stdout.Bytes()
|
||||
|
||||
if len(got) != len(want) {
|
||||
return fmt.Errorf("encodings have different length: got %d, want %d", len(got), len(want))
|
||||
}
|
||||
for i, g := range got {
|
||||
if w := want[i]; g != w {
|
||||
return fmt.Errorf("encodings differ at position 0x%x: got 0x%02x, want 0x%02x", i, g, w)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// encodePAM encodes gotImage in the PAM format.
|
||||
func encodePAM(gotImage image.Image) ([]byte, error) {
|
||||
m, ok := gotImage.(*image.NRGBA)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("lossless image did not decode to an *image.NRGBA")
|
||||
}
|
||||
b := m.Bounds()
|
||||
w, h := b.Dx(), b.Dy()
|
||||
buf := new(bytes.Buffer)
|
||||
fmt.Fprintf(buf, "P7\nWIDTH %d\nHEIGHT %d\nDEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n", w, h)
|
||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||
o := m.PixOffset(b.Min.X, y)
|
||||
buf.Write(m.Pix[o : o+4*w])
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// encodePGM encodes gotImage in the PGM format in the IMC4 layout.
|
||||
func encodePGM(gotImage image.Image) ([]byte, error) {
|
||||
var (
|
||||
m *image.YCbCr
|
||||
ma *nycbcra.Image
|
||||
)
|
||||
switch g := gotImage.(type) {
|
||||
case *image.YCbCr:
|
||||
m = g
|
||||
case *nycbcra.Image:
|
||||
m = &g.YCbCr
|
||||
ma = g
|
||||
default:
|
||||
return nil, fmt.Errorf("lossy image did not decode to an *image.YCbCr")
|
||||
}
|
||||
if m.SubsampleRatio != image.YCbCrSubsampleRatio420 {
|
||||
return nil, fmt.Errorf("lossy image did not decode to a 4:2:0 YCbCr")
|
||||
}
|
||||
b := m.Bounds()
|
||||
w, h := b.Dx(), b.Dy()
|
||||
w2, h2 := (w+1)/2, (h+1)/2
|
||||
outW, outH := 2*w2, h+h2
|
||||
if ma != nil {
|
||||
outH += h
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
fmt.Fprintf(buf, "P5\n%d %d\n255\n", outW, outH)
|
||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||
o := m.YOffset(b.Min.X, y)
|
||||
buf.Write(m.Y[o : o+w])
|
||||
if w&1 != 0 {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
}
|
||||
for y := b.Min.Y; y < b.Max.Y; y += 2 {
|
||||
o := m.COffset(b.Min.X, y)
|
||||
buf.Write(m.Cb[o : o+w2])
|
||||
buf.Write(m.Cr[o : o+w2])
|
||||
}
|
||||
if ma != nil {
|
||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||
o := ma.AOffset(b.Min.X, y)
|
||||
buf.Write(ma.A[o : o+w])
|
||||
if w&1 != 0 {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// dump can be useful for debugging.
|
||||
func dump(w io.Writer, b []byte) {
|
||||
h := hex.Dumper(w)
|
||||
h.Write(b)
|
||||
h.Close()
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
issuerepo: golang/go
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2015 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.
|
||||
|
||||
//go:generate go run gen.go
|
||||
|
||||
// Package colornames provides named colors as defined in the SVG 1.1 spec.
|
||||
//
|
||||
// See http://www.w3.org/TR/SVG/types.html#ColorKeywords
|
||||
package colornames
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright 2015 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 colornames
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestColornames(t *testing.T) {
|
||||
if len(Map) != len(Names) {
|
||||
t.Fatalf("Map and Names have different length: %d vs %d", len(Map), len(Names))
|
||||
}
|
||||
|
||||
for name, want := range testCases {
|
||||
got, ok := Map[name]
|
||||
if !ok {
|
||||
t.Errorf("Did not find %s", name)
|
||||
continue
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf("%s:\ngot %v\nwant %v", name, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var testCases = map[string]color.RGBA{
|
||||
"aliceblue": color.RGBA{240, 248, 255, 255},
|
||||
"crimson": color.RGBA{220, 20, 60, 255},
|
||||
"darkorange": color.RGBA{255, 140, 0, 255},
|
||||
"deepskyblue": color.RGBA{0, 191, 255, 255},
|
||||
"greenyellow": color.RGBA{173, 255, 47, 255},
|
||||
"lightgrey": color.RGBA{211, 211, 211, 255},
|
||||
"lightpink": color.RGBA{255, 182, 193, 255},
|
||||
"mediumseagreen": color.RGBA{60, 179, 113, 255},
|
||||
"olivedrab": color.RGBA{107, 142, 35, 255},
|
||||
"purple": color.RGBA{128, 0, 128, 255},
|
||||
"slategrey": color.RGBA{112, 128, 144, 255},
|
||||
"yellowgreen": color.RGBA{154, 205, 50, 255},
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
// Copyright 2015 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.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// This program generates table.go from
|
||||
// http://www.w3.org/TR/SVG/types.html#ColorKeywords
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"image/color"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
"golang.org/x/net/html/atom"
|
||||
)
|
||||
|
||||
// matchFunc matches HTML nodes.
|
||||
type matchFunc func(*html.Node) bool
|
||||
|
||||
// appendAll recursively traverses the parse tree rooted under the provided
|
||||
// node and appends all nodes matched by the matchFunc to dst.
|
||||
func appendAll(dst []*html.Node, n *html.Node, mf matchFunc) []*html.Node {
|
||||
if mf(n) {
|
||||
dst = append(dst, n)
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
dst = appendAll(dst, c, mf)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// matchAtom returns a matchFunc that matches a Node with the specified Atom.
|
||||
func matchAtom(a atom.Atom) matchFunc {
|
||||
return func(n *html.Node) bool {
|
||||
return n.DataAtom == a
|
||||
}
|
||||
}
|
||||
|
||||
// matchAtomAttr returns a matchFunc that matches a Node with the specified
|
||||
// Atom and a html.Attribute's namespace, key and value.
|
||||
func matchAtomAttr(a atom.Atom, namespace, key, value string) matchFunc {
|
||||
return func(n *html.Node) bool {
|
||||
return n.DataAtom == a && getAttr(n, namespace, key) == value
|
||||
}
|
||||
}
|
||||
|
||||
// getAttr fetches the value of a html.Attribute for a given namespace and key.
|
||||
func getAttr(n *html.Node, namespace, key string) string {
|
||||
for _, attr := range n.Attr {
|
||||
if attr.Namespace == namespace && attr.Key == key {
|
||||
return attr.Val
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// re extracts RGB values from strings like "rgb( 0, 223, 128)".
|
||||
var re = regexp.MustCompile(`rgb\(\s*([0-9]+),\s*([0-9]+),\s*([0-9]+)\)`)
|
||||
|
||||
// parseRGB parses a color from a string like "rgb( 0, 233, 128)". It sets
|
||||
// the alpha value of the color to full opacity.
|
||||
func parseRGB(s string) (color.RGBA, error) {
|
||||
m := re.FindStringSubmatch(s)
|
||||
if m == nil {
|
||||
return color.RGBA{}, fmt.Errorf("malformed color: %q", s)
|
||||
}
|
||||
var rgb [3]uint8
|
||||
for i, t := range m[1:] {
|
||||
num, err := strconv.ParseUint(t, 10, 8)
|
||||
if err != nil {
|
||||
return color.RGBA{}, fmt.Errorf("malformed value %q in %q: %s", t, s, err)
|
||||
}
|
||||
rgb[i] = uint8(num)
|
||||
}
|
||||
return color.RGBA{rgb[0], rgb[1], rgb[2], 0xFF}, nil
|
||||
}
|
||||
|
||||
// extractSVGColors extracts named colors from the parse tree of the SVG 1.1
|
||||
// spec HTML document "Chapter 4: Basic data types and interfaces".
|
||||
func extractSVGColors(tree *html.Node) (map[string]color.RGBA, error) {
|
||||
ret := make(map[string]color.RGBA)
|
||||
|
||||
// Find the tables which store the color keywords in the parse tree.
|
||||
colorTables := appendAll(nil, tree, func(n *html.Node) bool {
|
||||
return n.DataAtom == atom.Table && strings.Contains(getAttr(n, "", "summary"), "color keywords part")
|
||||
})
|
||||
|
||||
for _, table := range colorTables {
|
||||
// Color names and values are stored in TextNodes within spans in each row.
|
||||
for _, tr := range appendAll(nil, table, matchAtom(atom.Tr)) {
|
||||
nameSpan := appendAll(nil, tr, matchAtomAttr(atom.Span, "", "class", "prop-value"))
|
||||
valueSpan := appendAll(nil, tr, matchAtomAttr(atom.Span, "", "class", "color-keyword-value"))
|
||||
|
||||
// Since SVG 1.1 defines an odd number of colors, the last row
|
||||
// in the second table does not have contents. We skip it.
|
||||
if len(nameSpan) != 1 || len(valueSpan) != 1 {
|
||||
continue
|
||||
}
|
||||
n, v := nameSpan[0].FirstChild, valueSpan[0].FirstChild
|
||||
// This sanity checks for the existence of TextNodes under spans.
|
||||
if n == nil || n.Type != html.TextNode || v == nil || v.Type != html.TextNode {
|
||||
return nil, fmt.Errorf("extractSVGColors: couldn't find name/value text nodes")
|
||||
}
|
||||
val, err := parseRGB(v.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("extractSVGColors: couldn't parse name/value %q/%q: %s", n.Data, v.Data, err)
|
||||
}
|
||||
ret[n.Data] = val
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
const preamble = `// generated by go generate; DO NOT EDIT.
|
||||
|
||||
package colornames
|
||||
|
||||
import "image/color"
|
||||
|
||||
`
|
||||
|
||||
// WriteColorNames writes table.go.
|
||||
func writeColorNames(w io.Writer, m map[string]color.RGBA) {
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
fmt.Fprintln(w, preamble)
|
||||
fmt.Fprintln(w, "// Map contains named colors defined in the SVG 1.1 spec.")
|
||||
fmt.Fprintln(w, "var Map = map[string]color.RGBA{")
|
||||
for _, k := range keys {
|
||||
fmt.Fprintf(w, "%q:color.RGBA{%#02x, %#02x, %#02x, %#02x}, // rgb(%d, %d, %d)\n",
|
||||
k, m[k].R, m[k].G, m[k].B, m[k].A, m[k].R, m[k].G, m[k].B)
|
||||
}
|
||||
fmt.Fprintln(w, "}\n")
|
||||
fmt.Fprintln(w, "// Names contains the color names defined in the SVG 1.1 spec.")
|
||||
fmt.Fprintln(w, "var Names = []string{")
|
||||
for _, k := range keys {
|
||||
fmt.Fprintf(w, "%q,\n", k)
|
||||
}
|
||||
fmt.Fprintln(w, "}")
|
||||
}
|
||||
|
||||
const url = "http://www.w3.org/TR/SVG/types.html"
|
||||
|
||||
func main() {
|
||||
res, err := http.Get(url)
|
||||
if err != nil {
|
||||
log.Fatalf("Couldn't read from %s: %s\n", url, err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
tree, err := html.Parse(res.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("Couldn't parse %s: %s\n", url, err)
|
||||
}
|
||||
|
||||
colors, err := extractSVGColors(tree)
|
||||
if err != nil {
|
||||
log.Fatalf("Couldn't extract colors: %s\n", err)
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
writeColorNames(buf, colors)
|
||||
fmted, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
log.Fatalf("Error while formatting code: %s\n", err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile("table.go", fmted, 0644); err != nil {
|
||||
log.Fatalf("Error writing table.go: %s\n", err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,307 @@
|
|||
// generated by go generate; DO NOT EDIT.
|
||||
|
||||
package colornames
|
||||
|
||||
import "image/color"
|
||||
|
||||
// Map contains named colors defined in the SVG 1.1 spec.
|
||||
var Map = map[string]color.RGBA{
|
||||
"aliceblue": color.RGBA{0xf0, 0xf8, 0xff, 0xff}, // rgb(240, 248, 255)
|
||||
"antiquewhite": color.RGBA{0xfa, 0xeb, 0xd7, 0xff}, // rgb(250, 235, 215)
|
||||
"aqua": color.RGBA{0x00, 0xff, 0xff, 0xff}, // rgb(0, 255, 255)
|
||||
"aquamarine": color.RGBA{0x7f, 0xff, 0xd4, 0xff}, // rgb(127, 255, 212)
|
||||
"azure": color.RGBA{0xf0, 0xff, 0xff, 0xff}, // rgb(240, 255, 255)
|
||||
"beige": color.RGBA{0xf5, 0xf5, 0xdc, 0xff}, // rgb(245, 245, 220)
|
||||
"bisque": color.RGBA{0xff, 0xe4, 0xc4, 0xff}, // rgb(255, 228, 196)
|
||||
"black": color.RGBA{0x00, 0x00, 0x00, 0xff}, // rgb(0, 0, 0)
|
||||
"blanchedalmond": color.RGBA{0xff, 0xeb, 0xcd, 0xff}, // rgb(255, 235, 205)
|
||||
"blue": color.RGBA{0x00, 0x00, 0xff, 0xff}, // rgb(0, 0, 255)
|
||||
"blueviolet": color.RGBA{0x8a, 0x2b, 0xe2, 0xff}, // rgb(138, 43, 226)
|
||||
"brown": color.RGBA{0xa5, 0x2a, 0x2a, 0xff}, // rgb(165, 42, 42)
|
||||
"burlywood": color.RGBA{0xde, 0xb8, 0x87, 0xff}, // rgb(222, 184, 135)
|
||||
"cadetblue": color.RGBA{0x5f, 0x9e, 0xa0, 0xff}, // rgb(95, 158, 160)
|
||||
"chartreuse": color.RGBA{0x7f, 0xff, 0x00, 0xff}, // rgb(127, 255, 0)
|
||||
"chocolate": color.RGBA{0xd2, 0x69, 0x1e, 0xff}, // rgb(210, 105, 30)
|
||||
"coral": color.RGBA{0xff, 0x7f, 0x50, 0xff}, // rgb(255, 127, 80)
|
||||
"cornflowerblue": color.RGBA{0x64, 0x95, 0xed, 0xff}, // rgb(100, 149, 237)
|
||||
"cornsilk": color.RGBA{0xff, 0xf8, 0xdc, 0xff}, // rgb(255, 248, 220)
|
||||
"crimson": color.RGBA{0xdc, 0x14, 0x3c, 0xff}, // rgb(220, 20, 60)
|
||||
"cyan": color.RGBA{0x00, 0xff, 0xff, 0xff}, // rgb(0, 255, 255)
|
||||
"darkblue": color.RGBA{0x00, 0x00, 0x8b, 0xff}, // rgb(0, 0, 139)
|
||||
"darkcyan": color.RGBA{0x00, 0x8b, 0x8b, 0xff}, // rgb(0, 139, 139)
|
||||
"darkgoldenrod": color.RGBA{0xb8, 0x86, 0x0b, 0xff}, // rgb(184, 134, 11)
|
||||
"darkgray": color.RGBA{0xa9, 0xa9, 0xa9, 0xff}, // rgb(169, 169, 169)
|
||||
"darkgreen": color.RGBA{0x00, 0x64, 0x00, 0xff}, // rgb(0, 100, 0)
|
||||
"darkgrey": color.RGBA{0xa9, 0xa9, 0xa9, 0xff}, // rgb(169, 169, 169)
|
||||
"darkkhaki": color.RGBA{0xbd, 0xb7, 0x6b, 0xff}, // rgb(189, 183, 107)
|
||||
"darkmagenta": color.RGBA{0x8b, 0x00, 0x8b, 0xff}, // rgb(139, 0, 139)
|
||||
"darkolivegreen": color.RGBA{0x55, 0x6b, 0x2f, 0xff}, // rgb(85, 107, 47)
|
||||
"darkorange": color.RGBA{0xff, 0x8c, 0x00, 0xff}, // rgb(255, 140, 0)
|
||||
"darkorchid": color.RGBA{0x99, 0x32, 0xcc, 0xff}, // rgb(153, 50, 204)
|
||||
"darkred": color.RGBA{0x8b, 0x00, 0x00, 0xff}, // rgb(139, 0, 0)
|
||||
"darksalmon": color.RGBA{0xe9, 0x96, 0x7a, 0xff}, // rgb(233, 150, 122)
|
||||
"darkseagreen": color.RGBA{0x8f, 0xbc, 0x8f, 0xff}, // rgb(143, 188, 143)
|
||||
"darkslateblue": color.RGBA{0x48, 0x3d, 0x8b, 0xff}, // rgb(72, 61, 139)
|
||||
"darkslategray": color.RGBA{0x2f, 0x4f, 0x4f, 0xff}, // rgb(47, 79, 79)
|
||||
"darkslategrey": color.RGBA{0x2f, 0x4f, 0x4f, 0xff}, // rgb(47, 79, 79)
|
||||
"darkturquoise": color.RGBA{0x00, 0xce, 0xd1, 0xff}, // rgb(0, 206, 209)
|
||||
"darkviolet": color.RGBA{0x94, 0x00, 0xd3, 0xff}, // rgb(148, 0, 211)
|
||||
"deeppink": color.RGBA{0xff, 0x14, 0x93, 0xff}, // rgb(255, 20, 147)
|
||||
"deepskyblue": color.RGBA{0x00, 0xbf, 0xff, 0xff}, // rgb(0, 191, 255)
|
||||
"dimgray": color.RGBA{0x69, 0x69, 0x69, 0xff}, // rgb(105, 105, 105)
|
||||
"dimgrey": color.RGBA{0x69, 0x69, 0x69, 0xff}, // rgb(105, 105, 105)
|
||||
"dodgerblue": color.RGBA{0x1e, 0x90, 0xff, 0xff}, // rgb(30, 144, 255)
|
||||
"firebrick": color.RGBA{0xb2, 0x22, 0x22, 0xff}, // rgb(178, 34, 34)
|
||||
"floralwhite": color.RGBA{0xff, 0xfa, 0xf0, 0xff}, // rgb(255, 250, 240)
|
||||
"forestgreen": color.RGBA{0x22, 0x8b, 0x22, 0xff}, // rgb(34, 139, 34)
|
||||
"fuchsia": color.RGBA{0xff, 0x00, 0xff, 0xff}, // rgb(255, 0, 255)
|
||||
"gainsboro": color.RGBA{0xdc, 0xdc, 0xdc, 0xff}, // rgb(220, 220, 220)
|
||||
"ghostwhite": color.RGBA{0xf8, 0xf8, 0xff, 0xff}, // rgb(248, 248, 255)
|
||||
"gold": color.RGBA{0xff, 0xd7, 0x00, 0xff}, // rgb(255, 215, 0)
|
||||
"goldenrod": color.RGBA{0xda, 0xa5, 0x20, 0xff}, // rgb(218, 165, 32)
|
||||
"gray": color.RGBA{0x80, 0x80, 0x80, 0xff}, // rgb(128, 128, 128)
|
||||
"green": color.RGBA{0x00, 0x80, 0x00, 0xff}, // rgb(0, 128, 0)
|
||||
"greenyellow": color.RGBA{0xad, 0xff, 0x2f, 0xff}, // rgb(173, 255, 47)
|
||||
"grey": color.RGBA{0x80, 0x80, 0x80, 0xff}, // rgb(128, 128, 128)
|
||||
"honeydew": color.RGBA{0xf0, 0xff, 0xf0, 0xff}, // rgb(240, 255, 240)
|
||||
"hotpink": color.RGBA{0xff, 0x69, 0xb4, 0xff}, // rgb(255, 105, 180)
|
||||
"indianred": color.RGBA{0xcd, 0x5c, 0x5c, 0xff}, // rgb(205, 92, 92)
|
||||
"indigo": color.RGBA{0x4b, 0x00, 0x82, 0xff}, // rgb(75, 0, 130)
|
||||
"ivory": color.RGBA{0xff, 0xff, 0xf0, 0xff}, // rgb(255, 255, 240)
|
||||
"khaki": color.RGBA{0xf0, 0xe6, 0x8c, 0xff}, // rgb(240, 230, 140)
|
||||
"lavender": color.RGBA{0xe6, 0xe6, 0xfa, 0xff}, // rgb(230, 230, 250)
|
||||
"lavenderblush": color.RGBA{0xff, 0xf0, 0xf5, 0xff}, // rgb(255, 240, 245)
|
||||
"lawngreen": color.RGBA{0x7c, 0xfc, 0x00, 0xff}, // rgb(124, 252, 0)
|
||||
"lemonchiffon": color.RGBA{0xff, 0xfa, 0xcd, 0xff}, // rgb(255, 250, 205)
|
||||
"lightblue": color.RGBA{0xad, 0xd8, 0xe6, 0xff}, // rgb(173, 216, 230)
|
||||
"lightcoral": color.RGBA{0xf0, 0x80, 0x80, 0xff}, // rgb(240, 128, 128)
|
||||
"lightcyan": color.RGBA{0xe0, 0xff, 0xff, 0xff}, // rgb(224, 255, 255)
|
||||
"lightgoldenrodyellow": color.RGBA{0xfa, 0xfa, 0xd2, 0xff}, // rgb(250, 250, 210)
|
||||
"lightgray": color.RGBA{0xd3, 0xd3, 0xd3, 0xff}, // rgb(211, 211, 211)
|
||||
"lightgreen": color.RGBA{0x90, 0xee, 0x90, 0xff}, // rgb(144, 238, 144)
|
||||
"lightgrey": color.RGBA{0xd3, 0xd3, 0xd3, 0xff}, // rgb(211, 211, 211)
|
||||
"lightpink": color.RGBA{0xff, 0xb6, 0xc1, 0xff}, // rgb(255, 182, 193)
|
||||
"lightsalmon": color.RGBA{0xff, 0xa0, 0x7a, 0xff}, // rgb(255, 160, 122)
|
||||
"lightseagreen": color.RGBA{0x20, 0xb2, 0xaa, 0xff}, // rgb(32, 178, 170)
|
||||
"lightskyblue": color.RGBA{0x87, 0xce, 0xfa, 0xff}, // rgb(135, 206, 250)
|
||||
"lightslategray": color.RGBA{0x77, 0x88, 0x99, 0xff}, // rgb(119, 136, 153)
|
||||
"lightslategrey": color.RGBA{0x77, 0x88, 0x99, 0xff}, // rgb(119, 136, 153)
|
||||
"lightsteelblue": color.RGBA{0xb0, 0xc4, 0xde, 0xff}, // rgb(176, 196, 222)
|
||||
"lightyellow": color.RGBA{0xff, 0xff, 0xe0, 0xff}, // rgb(255, 255, 224)
|
||||
"lime": color.RGBA{0x00, 0xff, 0x00, 0xff}, // rgb(0, 255, 0)
|
||||
"limegreen": color.RGBA{0x32, 0xcd, 0x32, 0xff}, // rgb(50, 205, 50)
|
||||
"linen": color.RGBA{0xfa, 0xf0, 0xe6, 0xff}, // rgb(250, 240, 230)
|
||||
"magenta": color.RGBA{0xff, 0x00, 0xff, 0xff}, // rgb(255, 0, 255)
|
||||
"maroon": color.RGBA{0x80, 0x00, 0x00, 0xff}, // rgb(128, 0, 0)
|
||||
"mediumaquamarine": color.RGBA{0x66, 0xcd, 0xaa, 0xff}, // rgb(102, 205, 170)
|
||||
"mediumblue": color.RGBA{0x00, 0x00, 0xcd, 0xff}, // rgb(0, 0, 205)
|
||||
"mediumorchid": color.RGBA{0xba, 0x55, 0xd3, 0xff}, // rgb(186, 85, 211)
|
||||
"mediumpurple": color.RGBA{0x93, 0x70, 0xdb, 0xff}, // rgb(147, 112, 219)
|
||||
"mediumseagreen": color.RGBA{0x3c, 0xb3, 0x71, 0xff}, // rgb(60, 179, 113)
|
||||
"mediumslateblue": color.RGBA{0x7b, 0x68, 0xee, 0xff}, // rgb(123, 104, 238)
|
||||
"mediumspringgreen": color.RGBA{0x00, 0xfa, 0x9a, 0xff}, // rgb(0, 250, 154)
|
||||
"mediumturquoise": color.RGBA{0x48, 0xd1, 0xcc, 0xff}, // rgb(72, 209, 204)
|
||||
"mediumvioletred": color.RGBA{0xc7, 0x15, 0x85, 0xff}, // rgb(199, 21, 133)
|
||||
"midnightblue": color.RGBA{0x19, 0x19, 0x70, 0xff}, // rgb(25, 25, 112)
|
||||
"mintcream": color.RGBA{0xf5, 0xff, 0xfa, 0xff}, // rgb(245, 255, 250)
|
||||
"mistyrose": color.RGBA{0xff, 0xe4, 0xe1, 0xff}, // rgb(255, 228, 225)
|
||||
"moccasin": color.RGBA{0xff, 0xe4, 0xb5, 0xff}, // rgb(255, 228, 181)
|
||||
"navajowhite": color.RGBA{0xff, 0xde, 0xad, 0xff}, // rgb(255, 222, 173)
|
||||
"navy": color.RGBA{0x00, 0x00, 0x80, 0xff}, // rgb(0, 0, 128)
|
||||
"oldlace": color.RGBA{0xfd, 0xf5, 0xe6, 0xff}, // rgb(253, 245, 230)
|
||||
"olive": color.RGBA{0x80, 0x80, 0x00, 0xff}, // rgb(128, 128, 0)
|
||||
"olivedrab": color.RGBA{0x6b, 0x8e, 0x23, 0xff}, // rgb(107, 142, 35)
|
||||
"orange": color.RGBA{0xff, 0xa5, 0x00, 0xff}, // rgb(255, 165, 0)
|
||||
"orangered": color.RGBA{0xff, 0x45, 0x00, 0xff}, // rgb(255, 69, 0)
|
||||
"orchid": color.RGBA{0xda, 0x70, 0xd6, 0xff}, // rgb(218, 112, 214)
|
||||
"palegoldenrod": color.RGBA{0xee, 0xe8, 0xaa, 0xff}, // rgb(238, 232, 170)
|
||||
"palegreen": color.RGBA{0x98, 0xfb, 0x98, 0xff}, // rgb(152, 251, 152)
|
||||
"paleturquoise": color.RGBA{0xaf, 0xee, 0xee, 0xff}, // rgb(175, 238, 238)
|
||||
"palevioletred": color.RGBA{0xdb, 0x70, 0x93, 0xff}, // rgb(219, 112, 147)
|
||||
"papayawhip": color.RGBA{0xff, 0xef, 0xd5, 0xff}, // rgb(255, 239, 213)
|
||||
"peachpuff": color.RGBA{0xff, 0xda, 0xb9, 0xff}, // rgb(255, 218, 185)
|
||||
"peru": color.RGBA{0xcd, 0x85, 0x3f, 0xff}, // rgb(205, 133, 63)
|
||||
"pink": color.RGBA{0xff, 0xc0, 0xcb, 0xff}, // rgb(255, 192, 203)
|
||||
"plum": color.RGBA{0xdd, 0xa0, 0xdd, 0xff}, // rgb(221, 160, 221)
|
||||
"powderblue": color.RGBA{0xb0, 0xe0, 0xe6, 0xff}, // rgb(176, 224, 230)
|
||||
"purple": color.RGBA{0x80, 0x00, 0x80, 0xff}, // rgb(128, 0, 128)
|
||||
"red": color.RGBA{0xff, 0x00, 0x00, 0xff}, // rgb(255, 0, 0)
|
||||
"rosybrown": color.RGBA{0xbc, 0x8f, 0x8f, 0xff}, // rgb(188, 143, 143)
|
||||
"royalblue": color.RGBA{0x41, 0x69, 0xe1, 0xff}, // rgb(65, 105, 225)
|
||||
"saddlebrown": color.RGBA{0x8b, 0x45, 0x13, 0xff}, // rgb(139, 69, 19)
|
||||
"salmon": color.RGBA{0xfa, 0x80, 0x72, 0xff}, // rgb(250, 128, 114)
|
||||
"sandybrown": color.RGBA{0xf4, 0xa4, 0x60, 0xff}, // rgb(244, 164, 96)
|
||||
"seagreen": color.RGBA{0x2e, 0x8b, 0x57, 0xff}, // rgb(46, 139, 87)
|
||||
"seashell": color.RGBA{0xff, 0xf5, 0xee, 0xff}, // rgb(255, 245, 238)
|
||||
"sienna": color.RGBA{0xa0, 0x52, 0x2d, 0xff}, // rgb(160, 82, 45)
|
||||
"silver": color.RGBA{0xc0, 0xc0, 0xc0, 0xff}, // rgb(192, 192, 192)
|
||||
"skyblue": color.RGBA{0x87, 0xce, 0xeb, 0xff}, // rgb(135, 206, 235)
|
||||
"slateblue": color.RGBA{0x6a, 0x5a, 0xcd, 0xff}, // rgb(106, 90, 205)
|
||||
"slategray": color.RGBA{0x70, 0x80, 0x90, 0xff}, // rgb(112, 128, 144)
|
||||
"slategrey": color.RGBA{0x70, 0x80, 0x90, 0xff}, // rgb(112, 128, 144)
|
||||
"snow": color.RGBA{0xff, 0xfa, 0xfa, 0xff}, // rgb(255, 250, 250)
|
||||
"springgreen": color.RGBA{0x00, 0xff, 0x7f, 0xff}, // rgb(0, 255, 127)
|
||||
"steelblue": color.RGBA{0x46, 0x82, 0xb4, 0xff}, // rgb(70, 130, 180)
|
||||
"tan": color.RGBA{0xd2, 0xb4, 0x8c, 0xff}, // rgb(210, 180, 140)
|
||||
"teal": color.RGBA{0x00, 0x80, 0x80, 0xff}, // rgb(0, 128, 128)
|
||||
"thistle": color.RGBA{0xd8, 0xbf, 0xd8, 0xff}, // rgb(216, 191, 216)
|
||||
"tomato": color.RGBA{0xff, 0x63, 0x47, 0xff}, // rgb(255, 99, 71)
|
||||
"turquoise": color.RGBA{0x40, 0xe0, 0xd0, 0xff}, // rgb(64, 224, 208)
|
||||
"violet": color.RGBA{0xee, 0x82, 0xee, 0xff}, // rgb(238, 130, 238)
|
||||
"wheat": color.RGBA{0xf5, 0xde, 0xb3, 0xff}, // rgb(245, 222, 179)
|
||||
"white": color.RGBA{0xff, 0xff, 0xff, 0xff}, // rgb(255, 255, 255)
|
||||
"whitesmoke": color.RGBA{0xf5, 0xf5, 0xf5, 0xff}, // rgb(245, 245, 245)
|
||||
"yellow": color.RGBA{0xff, 0xff, 0x00, 0xff}, // rgb(255, 255, 0)
|
||||
"yellowgreen": color.RGBA{0x9a, 0xcd, 0x32, 0xff}, // rgb(154, 205, 50)
|
||||
}
|
||||
|
||||
// Names contains the color names defined in the SVG 1.1 spec.
|
||||
var Names = []string{
|
||||
"aliceblue",
|
||||
"antiquewhite",
|
||||
"aqua",
|
||||
"aquamarine",
|
||||
"azure",
|
||||
"beige",
|
||||
"bisque",
|
||||
"black",
|
||||
"blanchedalmond",
|
||||
"blue",
|
||||
"blueviolet",
|
||||
"brown",
|
||||
"burlywood",
|
||||
"cadetblue",
|
||||
"chartreuse",
|
||||
"chocolate",
|
||||
"coral",
|
||||
"cornflowerblue",
|
||||
"cornsilk",
|
||||
"crimson",
|
||||
"cyan",
|
||||
"darkblue",
|
||||
"darkcyan",
|
||||
"darkgoldenrod",
|
||||
"darkgray",
|
||||
"darkgreen",
|
||||
"darkgrey",
|
||||
"darkkhaki",
|
||||
"darkmagenta",
|
||||
"darkolivegreen",
|
||||
"darkorange",
|
||||
"darkorchid",
|
||||
"darkred",
|
||||
"darksalmon",
|
||||
"darkseagreen",
|
||||
"darkslateblue",
|
||||
"darkslategray",
|
||||
"darkslategrey",
|
||||
"darkturquoise",
|
||||
"darkviolet",
|
||||
"deeppink",
|
||||
"deepskyblue",
|
||||
"dimgray",
|
||||
"dimgrey",
|
||||
"dodgerblue",
|
||||
"firebrick",
|
||||
"floralwhite",
|
||||
"forestgreen",
|
||||
"fuchsia",
|
||||
"gainsboro",
|
||||
"ghostwhite",
|
||||
"gold",
|
||||
"goldenrod",
|
||||
"gray",
|
||||
"green",
|
||||
"greenyellow",
|
||||
"grey",
|
||||
"honeydew",
|
||||
"hotpink",
|
||||
"indianred",
|
||||
"indigo",
|
||||
"ivory",
|
||||
"khaki",
|
||||
"lavender",
|
||||
"lavenderblush",
|
||||
"lawngreen",
|
||||
"lemonchiffon",
|
||||
"lightblue",
|
||||
"lightcoral",
|
||||
"lightcyan",
|
||||
"lightgoldenrodyellow",
|
||||
"lightgray",
|
||||
"lightgreen",
|
||||
"lightgrey",
|
||||
"lightpink",
|
||||
"lightsalmon",
|
||||
"lightseagreen",
|
||||
"lightskyblue",
|
||||
"lightslategray",
|
||||
"lightslategrey",
|
||||
"lightsteelblue",
|
||||
"lightyellow",
|
||||
"lime",
|
||||
"limegreen",
|
||||
"linen",
|
||||
"magenta",
|
||||
"maroon",
|
||||
"mediumaquamarine",
|
||||
"mediumblue",
|
||||
"mediumorchid",
|
||||
"mediumpurple",
|
||||
"mediumseagreen",
|
||||
"mediumslateblue",
|
||||
"mediumspringgreen",
|
||||
"mediumturquoise",
|
||||
"mediumvioletred",
|
||||
"midnightblue",
|
||||
"mintcream",
|
||||
"mistyrose",
|
||||
"moccasin",
|
||||
"navajowhite",
|
||||
"navy",
|
||||
"oldlace",
|
||||
"olive",
|
||||
"olivedrab",
|
||||
"orange",
|
||||
"orangered",
|
||||
"orchid",
|
||||
"palegoldenrod",
|
||||
"palegreen",
|
||||
"paleturquoise",
|
||||
"palevioletred",
|
||||
"papayawhip",
|
||||
"peachpuff",
|
||||
"peru",
|
||||
"pink",
|
||||
"plum",
|
||||
"powderblue",
|
||||
"purple",
|
||||
"red",
|
||||
"rosybrown",
|
||||
"royalblue",
|
||||
"saddlebrown",
|
||||
"salmon",
|
||||
"sandybrown",
|
||||
"seagreen",
|
||||
"seashell",
|
||||
"sienna",
|
||||
"silver",
|
||||
"skyblue",
|
||||
"slateblue",
|
||||
"slategray",
|
||||
"slategrey",
|
||||
"snow",
|
||||
"springgreen",
|
||||
"steelblue",
|
||||
"tan",
|
||||
"teal",
|
||||
"thistle",
|
||||
"tomato",
|
||||
"turquoise",
|
||||
"violet",
|
||||
"wheat",
|
||||
"white",
|
||||
"whitesmoke",
|
||||
"yellow",
|
||||
"yellowgreen",
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright 2015 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 draw provides image composition functions.
|
||||
//
|
||||
// See "The Go image/draw package" for an introduction to this package:
|
||||
// http://golang.org/doc/articles/image_draw.html
|
||||
//
|
||||
// This package is a superset of and a drop-in replacement for the image/draw
|
||||
// package in the standard library.
|
||||
package draw
|
||||
|
||||
// This file just contains the API exported by the image/draw package in the
|
||||
// standard library. Other files in this package provide additional features.
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
)
|
||||
|
||||
// Draw calls DrawMask with a nil mask.
|
||||
func Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point, op Op) {
|
||||
draw.Draw(dst, r, src, sp, draw.Op(op))
|
||||
}
|
||||
|
||||
// DrawMask aligns r.Min in dst with sp in src and mp in mask and then
|
||||
// replaces the rectangle r in dst with the result of a Porter-Duff
|
||||
// composition. A nil mask is treated as opaque.
|
||||
func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) {
|
||||
draw.DrawMask(dst, r, src, sp, mask, mp, draw.Op(op))
|
||||
}
|
||||
|
||||
// Drawer contains the Draw method.
|
||||
type Drawer interface {
|
||||
// Draw aligns r.Min in dst with sp in src and then replaces the
|
||||
// rectangle r in dst with the result of drawing src on dst.
|
||||
Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point)
|
||||
}
|
||||
|
||||
// FloydSteinberg is a Drawer that is the Src Op with Floyd-Steinberg error
|
||||
// diffusion.
|
||||
var FloydSteinberg Drawer = floydSteinberg{}
|
||||
|
||||
type floydSteinberg struct{}
|
||||
|
||||
func (floydSteinberg) Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point) {
|
||||
draw.FloydSteinberg.Draw(dst, r, src, sp)
|
||||
}
|
||||
|
||||
// Image is an image.Image with a Set method to change a single pixel.
|
||||
type Image interface {
|
||||
image.Image
|
||||
Set(x, y int, c color.Color)
|
||||
}
|
||||
|
||||
// Op is a Porter-Duff compositing operator.
|
||||
type Op int
|
||||
|
||||
const (
|
||||
// Over specifies ``(src in mask) over dst''.
|
||||
Over Op = Op(draw.Over)
|
||||
// Src specifies ``src in mask''.
|
||||
Src Op = Op(draw.Src)
|
||||
)
|
||||
|
||||
// Draw implements the Drawer interface by calling the Draw function with
|
||||
// this Op.
|
||||
func (op Op) Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point) {
|
||||
(draw.Op(op)).Draw(dst, r, src, sp)
|
||||
}
|
||||
|
||||
// Quantizer produces a palette for an image.
|
||||
type Quantizer interface {
|
||||
// Quantize appends up to cap(p) - len(p) colors to p and returns the
|
||||
// updated palette suitable for converting m to a paletted image.
|
||||
Quantize(p color.Palette, m image.Image) color.Palette
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
// Copyright 2015 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 draw_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
|
||||
"golang.org/x/image/draw"
|
||||
"golang.org/x/image/math/f64"
|
||||
)
|
||||
|
||||
func ExampleDraw() {
|
||||
fSrc, err := os.Open("../testdata/blue-purple-pink.png")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer fSrc.Close()
|
||||
src, err := png.Decode(fSrc)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
dst := image.NewRGBA(image.Rect(0, 0, 400, 300))
|
||||
green := image.NewUniform(color.RGBA{0x00, 0x1f, 0x00, 0xff})
|
||||
draw.Copy(dst, image.Point{}, green, dst.Bounds(), draw.Src, nil)
|
||||
qs := []draw.Interpolator{
|
||||
draw.NearestNeighbor,
|
||||
draw.ApproxBiLinear,
|
||||
draw.CatmullRom,
|
||||
}
|
||||
const cos60, sin60 = 0.5, 0.866025404
|
||||
t := f64.Aff3{
|
||||
+2 * cos60, -2 * sin60, 100,
|
||||
+2 * sin60, +2 * cos60, 100,
|
||||
}
|
||||
|
||||
draw.Copy(dst, image.Point{20, 30}, src, src.Bounds(), draw.Over, nil)
|
||||
for i, q := range qs {
|
||||
q.Scale(dst, image.Rect(200+10*i, 100*i, 600+10*i, 150+100*i), src, src.Bounds(), draw.Over, nil)
|
||||
}
|
||||
draw.NearestNeighbor.Transform(dst, t, src, src.Bounds(), draw.Over, nil)
|
||||
|
||||
red := image.NewNRGBA(image.Rect(0, 0, 16, 16))
|
||||
for y := 0; y < 16; y++ {
|
||||
for x := 0; x < 16; x++ {
|
||||
red.SetNRGBA(x, y, color.NRGBA{
|
||||
R: uint8(x * 0x11),
|
||||
A: uint8(y * 0x11),
|
||||
})
|
||||
}
|
||||
}
|
||||
red.SetNRGBA(0, 0, color.NRGBA{0xff, 0xff, 0x00, 0xff})
|
||||
red.SetNRGBA(15, 15, color.NRGBA{0xff, 0xff, 0x00, 0xff})
|
||||
|
||||
ops := []draw.Op{
|
||||
draw.Over,
|
||||
draw.Src,
|
||||
}
|
||||
for i, op := range ops {
|
||||
dr := image.Rect(120+10*i, 150+60*i, 170+10*i, 200+60*i)
|
||||
draw.NearestNeighbor.Scale(dst, dr, red, red.Bounds(), op, nil)
|
||||
t := f64.Aff3{
|
||||
+cos60, -sin60, float64(190 + 10*i),
|
||||
+sin60, +cos60, float64(140 + 50*i),
|
||||
}
|
||||
draw.NearestNeighbor.Transform(dst, t, red, red.Bounds(), op, nil)
|
||||
}
|
||||
|
||||
dr := image.Rect(0, 0, 128, 128)
|
||||
checkerboard := image.NewAlpha(dr)
|
||||
for y := dr.Min.Y; y < dr.Max.Y; y++ {
|
||||
for x := dr.Min.X; x < dr.Max.X; x++ {
|
||||
if (x/20)%2 == (y/20)%2 {
|
||||
checkerboard.SetAlpha(x, y, color.Alpha{0xff})
|
||||
}
|
||||
}
|
||||
}
|
||||
sr := image.Rect(0, 0, 16, 16)
|
||||
circle := image.NewAlpha(sr)
|
||||
for y := sr.Min.Y; y < sr.Max.Y; y++ {
|
||||
for x := sr.Min.X; x < sr.Max.X; x++ {
|
||||
dx, dy := x-10, y-8
|
||||
if d := 32 * math.Sqrt(float64(dx*dx)+float64(dy*dy)); d < 0xff {
|
||||
circle.SetAlpha(x, y, color.Alpha{0xff - uint8(d)})
|
||||
}
|
||||
}
|
||||
}
|
||||
cyan := image.NewUniform(color.RGBA{0x00, 0xff, 0xff, 0xff})
|
||||
draw.NearestNeighbor.Scale(dst, dr, cyan, sr, draw.Over, &draw.Options{
|
||||
DstMask: checkerboard,
|
||||
SrcMask: circle,
|
||||
})
|
||||
|
||||
// Change false to true to write the resultant image to disk.
|
||||
if false {
|
||||
fDst, err := os.Create("out.png")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer fDst.Close()
|
||||
err = png.Encode(fDst, dst)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("dst has bounds %v.\n", dst.Bounds())
|
||||
// Output:
|
||||
// dst has bounds (0,0)-(400,300).
|
||||
}
|
|
@ -0,0 +1,527 @@
|
|||
// Copyright 2015 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.
|
||||
|
||||
//go:generate go run gen.go
|
||||
|
||||
package draw
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/image/math/f64"
|
||||
)
|
||||
|
||||
// Copy copies the part of the source image defined by src and sr and writes
|
||||
// the result of a Porter-Duff composition to the part of the destination image
|
||||
// defined by dst and the translation of sr so that sr.Min translates to dp.
|
||||
func Copy(dst Image, dp image.Point, src image.Image, sr image.Rectangle, op Op, opts *Options) {
|
||||
var o Options
|
||||
if opts != nil {
|
||||
o = *opts
|
||||
}
|
||||
dr := sr.Add(dp.Sub(sr.Min))
|
||||
if o.DstMask == nil {
|
||||
DrawMask(dst, dr, src, sr.Min, o.SrcMask, o.SrcMaskP.Add(sr.Min), op)
|
||||
} else {
|
||||
NearestNeighbor.Scale(dst, dr, src, sr, op, opts)
|
||||
}
|
||||
}
|
||||
|
||||
// Scaler scales the part of the source image defined by src and sr and writes
|
||||
// the result of a Porter-Duff composition to the part of the destination image
|
||||
// defined by dst and dr.
|
||||
//
|
||||
// A Scaler is safe to use concurrently.
|
||||
type Scaler interface {
|
||||
Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options)
|
||||
}
|
||||
|
||||
// Transformer transforms the part of the source image defined by src and sr
|
||||
// and writes the result of a Porter-Duff composition to the part of the
|
||||
// destination image defined by dst and the affine transform m applied to sr.
|
||||
//
|
||||
// For example, if m is the matrix
|
||||
//
|
||||
// m00 m01 m02
|
||||
// m10 m11 m12
|
||||
//
|
||||
// then the src-space point (sx, sy) maps to the dst-space point
|
||||
// (m00*sx + m01*sy + m02, m10*sx + m11*sy + m12).
|
||||
//
|
||||
// A Transformer is safe to use concurrently.
|
||||
type Transformer interface {
|
||||
Transform(dst Image, m f64.Aff3, src image.Image, sr image.Rectangle, op Op, opts *Options)
|
||||
}
|
||||
|
||||
// Options are optional parameters to Copy, Scale and Transform.
|
||||
//
|
||||
// A nil *Options means to use the default (zero) values of each field.
|
||||
type Options struct {
|
||||
// Masks limit what parts of the dst image are drawn to and what parts of
|
||||
// the src image are drawn from.
|
||||
//
|
||||
// A dst or src mask image having a zero alpha (transparent) pixel value in
|
||||
// the respective coordinate space means that that dst pixel is entirely
|
||||
// unaffected or that src pixel is considered transparent black. A full
|
||||
// alpha (opaque) value means that the dst pixel is maximally affected or
|
||||
// the src pixel contributes maximally. The default values, nil, are
|
||||
// equivalent to fully opaque, infinitely large mask images.
|
||||
//
|
||||
// The DstMask is otherwise known as a clip mask, and its pixels map 1:1 to
|
||||
// the dst image's pixels. DstMaskP in DstMask space corresponds to
|
||||
// image.Point{X:0, Y:0} in dst space. For example, when limiting
|
||||
// repainting to a 'dirty rectangle', use that image.Rectangle and a zero
|
||||
// image.Point as the DstMask and DstMaskP.
|
||||
//
|
||||
// The SrcMask's pixels map 1:1 to the src image's pixels. SrcMaskP in
|
||||
// SrcMask space corresponds to image.Point{X:0, Y:0} in src space. For
|
||||
// example, when drawing font glyphs in a uniform color, use an
|
||||
// *image.Uniform as the src, and use the glyph atlas image and the
|
||||
// per-glyph offset as SrcMask and SrcMaskP:
|
||||
// Copy(dst, dp, image.NewUniform(color), image.Rect(0, 0, glyphWidth, glyphHeight), &Options{
|
||||
// SrcMask: glyphAtlas,
|
||||
// SrcMaskP: glyphOffset,
|
||||
// })
|
||||
DstMask image.Image
|
||||
DstMaskP image.Point
|
||||
SrcMask image.Image
|
||||
SrcMaskP image.Point
|
||||
|
||||
// TODO: a smooth vs sharp edges option, for arbitrary rotations?
|
||||
}
|
||||
|
||||
// Interpolator is an interpolation algorithm, when dst and src pixels don't
|
||||
// have a 1:1 correspondence.
|
||||
//
|
||||
// Of the interpolators provided by this package:
|
||||
// - NearestNeighbor is fast but usually looks worst.
|
||||
// - CatmullRom is slow but usually looks best.
|
||||
// - ApproxBiLinear has reasonable speed and quality.
|
||||
//
|
||||
// The time taken depends on the size of dr. For kernel interpolators, the
|
||||
// speed also depends on the size of sr, and so are often slower than
|
||||
// non-kernel interpolators, especially when scaling down.
|
||||
type Interpolator interface {
|
||||
Scaler
|
||||
Transformer
|
||||
}
|
||||
|
||||
// Kernel is an interpolator that blends source pixels weighted by a symmetric
|
||||
// kernel function.
|
||||
type Kernel struct {
|
||||
// Support is the kernel support and must be >= 0. At(t) is assumed to be
|
||||
// zero when t >= Support.
|
||||
Support float64
|
||||
// At is the kernel function. It will only be called with t in the
|
||||
// range [0, Support).
|
||||
At func(t float64) float64
|
||||
}
|
||||
|
||||
// Scale implements the Scaler interface.
|
||||
func (q *Kernel) Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) {
|
||||
q.newScaler(dr.Dx(), dr.Dy(), sr.Dx(), sr.Dy(), false).Scale(dst, dr, src, sr, op, opts)
|
||||
}
|
||||
|
||||
// NewScaler returns a Scaler that is optimized for scaling multiple times with
|
||||
// the same fixed destination and source width and height.
|
||||
func (q *Kernel) NewScaler(dw, dh, sw, sh int) Scaler {
|
||||
return q.newScaler(dw, dh, sw, sh, true)
|
||||
}
|
||||
|
||||
func (q *Kernel) newScaler(dw, dh, sw, sh int, usePool bool) Scaler {
|
||||
z := &kernelScaler{
|
||||
kernel: q,
|
||||
dw: int32(dw),
|
||||
dh: int32(dh),
|
||||
sw: int32(sw),
|
||||
sh: int32(sh),
|
||||
horizontal: newDistrib(q, int32(dw), int32(sw)),
|
||||
vertical: newDistrib(q, int32(dh), int32(sh)),
|
||||
}
|
||||
if usePool {
|
||||
z.pool.New = func() interface{} {
|
||||
tmp := z.makeTmpBuf()
|
||||
return &tmp
|
||||
}
|
||||
}
|
||||
return z
|
||||
}
|
||||
|
||||
var (
|
||||
// NearestNeighbor is the nearest neighbor interpolator. It is very fast,
|
||||
// but usually gives very low quality results. When scaling up, the result
|
||||
// will look 'blocky'.
|
||||
NearestNeighbor = Interpolator(nnInterpolator{})
|
||||
|
||||
// ApproxBiLinear is a mixture of the nearest neighbor and bi-linear
|
||||
// interpolators. It is fast, but usually gives medium quality results.
|
||||
//
|
||||
// It implements bi-linear interpolation when upscaling and a bi-linear
|
||||
// blend of the 4 nearest neighbor pixels when downscaling. This yields
|
||||
// nicer quality than nearest neighbor interpolation when upscaling, but
|
||||
// the time taken is independent of the number of source pixels, unlike the
|
||||
// bi-linear interpolator. When downscaling a large image, the performance
|
||||
// difference can be significant.
|
||||
ApproxBiLinear = Interpolator(ablInterpolator{})
|
||||
|
||||
// BiLinear is the tent kernel. It is slow, but usually gives high quality
|
||||
// results.
|
||||
BiLinear = &Kernel{1, func(t float64) float64 {
|
||||
return 1 - t
|
||||
}}
|
||||
|
||||
// CatmullRom is the Catmull-Rom kernel. It is very slow, but usually gives
|
||||
// very high quality results.
|
||||
//
|
||||
// It is an instance of the more general cubic BC-spline kernel with parameters
|
||||
// B=0 and C=0.5. See Mitchell and Netravali, "Reconstruction Filters in
|
||||
// Computer Graphics", Computer Graphics, Vol. 22, No. 4, pp. 221-228.
|
||||
CatmullRom = &Kernel{2, func(t float64) float64 {
|
||||
if t < 1 {
|
||||
return (1.5*t-2.5)*t*t + 1
|
||||
}
|
||||
return ((-0.5*t+2.5)*t-4)*t + 2
|
||||
}}
|
||||
|
||||
// TODO: a Kaiser-Bessel kernel?
|
||||
)
|
||||
|
||||
type nnInterpolator struct{}
|
||||
|
||||
type ablInterpolator struct{}
|
||||
|
||||
type kernelScaler struct {
|
||||
kernel *Kernel
|
||||
dw, dh, sw, sh int32
|
||||
horizontal, vertical distrib
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
func (z *kernelScaler) makeTmpBuf() [][4]float64 {
|
||||
return make([][4]float64, z.dw*z.sh)
|
||||
}
|
||||
|
||||
// source is a range of contribs, their inverse total weight, and that ITW
|
||||
// divided by 0xffff.
|
||||
type source struct {
|
||||
i, j int32
|
||||
invTotalWeight float64
|
||||
invTotalWeightFFFF float64
|
||||
}
|
||||
|
||||
// contrib is the weight of a column or row.
|
||||
type contrib struct {
|
||||
coord int32
|
||||
weight float64
|
||||
}
|
||||
|
||||
// distrib measures how source pixels are distributed over destination pixels.
|
||||
type distrib struct {
|
||||
// sources are what contribs each column or row in the source image owns,
|
||||
// and the total weight of those contribs.
|
||||
sources []source
|
||||
// contribs are the contributions indexed by sources[s].i and sources[s].j.
|
||||
contribs []contrib
|
||||
}
|
||||
|
||||
// newDistrib returns a distrib that distributes sw source columns (or rows)
|
||||
// over dw destination columns (or rows).
|
||||
func newDistrib(q *Kernel, dw, sw int32) distrib {
|
||||
scale := float64(sw) / float64(dw)
|
||||
halfWidth, kernelArgScale := q.Support, 1.0
|
||||
// When shrinking, broaden the effective kernel support so that we still
|
||||
// visit every source pixel.
|
||||
if scale > 1 {
|
||||
halfWidth *= scale
|
||||
kernelArgScale = 1 / scale
|
||||
}
|
||||
|
||||
// Make the sources slice, one source for each column or row, and temporarily
|
||||
// appropriate its elements' fields so that invTotalWeight is the scaled
|
||||
// coordinate of the source column or row, and i and j are the lower and
|
||||
// upper bounds of the range of destination columns or rows affected by the
|
||||
// source column or row.
|
||||
n, sources := int32(0), make([]source, dw)
|
||||
for x := range sources {
|
||||
center := (float64(x)+0.5)*scale - 0.5
|
||||
i := int32(math.Floor(center - halfWidth))
|
||||
if i < 0 {
|
||||
i = 0
|
||||
}
|
||||
j := int32(math.Ceil(center + halfWidth))
|
||||
if j > sw {
|
||||
j = sw
|
||||
if j < i {
|
||||
j = i
|
||||
}
|
||||
}
|
||||
sources[x] = source{i: i, j: j, invTotalWeight: center}
|
||||
n += j - i
|
||||
}
|
||||
|
||||
contribs := make([]contrib, 0, n)
|
||||
for k, b := range sources {
|
||||
totalWeight := 0.0
|
||||
l := int32(len(contribs))
|
||||
for coord := b.i; coord < b.j; coord++ {
|
||||
t := abs((b.invTotalWeight - float64(coord)) * kernelArgScale)
|
||||
if t >= q.Support {
|
||||
continue
|
||||
}
|
||||
weight := q.At(t)
|
||||
if weight == 0 {
|
||||
continue
|
||||
}
|
||||
totalWeight += weight
|
||||
contribs = append(contribs, contrib{coord, weight})
|
||||
}
|
||||
totalWeight = 1 / totalWeight
|
||||
sources[k] = source{
|
||||
i: l,
|
||||
j: int32(len(contribs)),
|
||||
invTotalWeight: totalWeight,
|
||||
invTotalWeightFFFF: totalWeight / 0xffff,
|
||||
}
|
||||
}
|
||||
|
||||
return distrib{sources, contribs}
|
||||
}
|
||||
|
||||
// abs is like math.Abs, but it doesn't care about negative zero, infinities or
|
||||
// NaNs.
|
||||
func abs(f float64) float64 {
|
||||
if f < 0 {
|
||||
f = -f
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// ftou converts the range [0.0, 1.0] to [0, 0xffff].
|
||||
func ftou(f float64) uint16 {
|
||||
i := int32(0xffff*f + 0.5)
|
||||
if i > 0xffff {
|
||||
return 0xffff
|
||||
}
|
||||
if i > 0 {
|
||||
return uint16(i)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// fffftou converts the range [0.0, 65535.0] to [0, 0xffff].
|
||||
func fffftou(f float64) uint16 {
|
||||
i := int32(f + 0.5)
|
||||
if i > 0xffff {
|
||||
return 0xffff
|
||||
}
|
||||
if i > 0 {
|
||||
return uint16(i)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// invert returns the inverse of m.
|
||||
//
|
||||
// TODO: move this into the f64 package, once we work out the convention for
|
||||
// matrix methods in that package: do they modify the receiver, take a dst
|
||||
// pointer argument, or return a new value?
|
||||
func invert(m *f64.Aff3) f64.Aff3 {
|
||||
m00 := +m[3*1+1]
|
||||
m01 := -m[3*0+1]
|
||||
m02 := +m[3*1+2]*m[3*0+1] - m[3*1+1]*m[3*0+2]
|
||||
m10 := -m[3*1+0]
|
||||
m11 := +m[3*0+0]
|
||||
m12 := +m[3*1+0]*m[3*0+2] - m[3*1+2]*m[3*0+0]
|
||||
|
||||
det := m00*m11 - m10*m01
|
||||
|
||||
return f64.Aff3{
|
||||
m00 / det,
|
||||
m01 / det,
|
||||
m02 / det,
|
||||
m10 / det,
|
||||
m11 / det,
|
||||
m12 / det,
|
||||
}
|
||||
}
|
||||
|
||||
func matMul(p, q *f64.Aff3) f64.Aff3 {
|
||||
return f64.Aff3{
|
||||
p[3*0+0]*q[3*0+0] + p[3*0+1]*q[3*1+0],
|
||||
p[3*0+0]*q[3*0+1] + p[3*0+1]*q[3*1+1],
|
||||
p[3*0+0]*q[3*0+2] + p[3*0+1]*q[3*1+2] + p[3*0+2],
|
||||
p[3*1+0]*q[3*0+0] + p[3*1+1]*q[3*1+0],
|
||||
p[3*1+0]*q[3*0+1] + p[3*1+1]*q[3*1+1],
|
||||
p[3*1+0]*q[3*0+2] + p[3*1+1]*q[3*1+2] + p[3*1+2],
|
||||
}
|
||||
}
|
||||
|
||||
// transformRect returns a rectangle dr that contains sr transformed by s2d.
|
||||
func transformRect(s2d *f64.Aff3, sr *image.Rectangle) (dr image.Rectangle) {
|
||||
ps := [...]image.Point{
|
||||
{sr.Min.X, sr.Min.Y},
|
||||
{sr.Max.X, sr.Min.Y},
|
||||
{sr.Min.X, sr.Max.Y},
|
||||
{sr.Max.X, sr.Max.Y},
|
||||
}
|
||||
for i, p := range ps {
|
||||
sxf := float64(p.X)
|
||||
syf := float64(p.Y)
|
||||
dx := int(math.Floor(s2d[0]*sxf + s2d[1]*syf + s2d[2]))
|
||||
dy := int(math.Floor(s2d[3]*sxf + s2d[4]*syf + s2d[5]))
|
||||
|
||||
// The +1 adjustments below are because an image.Rectangle is inclusive
|
||||
// on the low end but exclusive on the high end.
|
||||
|
||||
if i == 0 {
|
||||
dr = image.Rectangle{
|
||||
Min: image.Point{dx + 0, dy + 0},
|
||||
Max: image.Point{dx + 1, dy + 1},
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if dr.Min.X > dx {
|
||||
dr.Min.X = dx
|
||||
}
|
||||
dx++
|
||||
if dr.Max.X < dx {
|
||||
dr.Max.X = dx
|
||||
}
|
||||
|
||||
if dr.Min.Y > dy {
|
||||
dr.Min.Y = dy
|
||||
}
|
||||
dy++
|
||||
if dr.Max.Y < dy {
|
||||
dr.Max.Y = dy
|
||||
}
|
||||
}
|
||||
return dr
|
||||
}
|
||||
|
||||
func clipAffectedDestRect(adr image.Rectangle, dstMask image.Image, dstMaskP image.Point) (image.Rectangle, image.Image) {
|
||||
if dstMask == nil {
|
||||
return adr, nil
|
||||
}
|
||||
// TODO: enable this fast path once Go 1.5 is released, where an
|
||||
// image.Rectangle implements image.Image.
|
||||
// if r, ok := dstMask.(image.Rectangle); ok {
|
||||
// return adr.Intersect(r.Sub(dstMaskP)), nil
|
||||
// }
|
||||
// TODO: clip to dstMask.Bounds() if the color model implies that out-of-bounds means 0 alpha?
|
||||
return adr, dstMask
|
||||
}
|
||||
|
||||
func transform_Uniform(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.Uniform, sr image.Rectangle, bias image.Point, op Op) {
|
||||
switch op {
|
||||
case Over:
|
||||
switch dst := dst.(type) {
|
||||
case *image.RGBA:
|
||||
pr, pg, pb, pa := src.C.RGBA()
|
||||
pa1 := (0xffff - pa) * 0x101
|
||||
|
||||
for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
|
||||
dyf := float64(dr.Min.Y+int(dy)) + 0.5
|
||||
d := dst.PixOffset(dr.Min.X+adr.Min.X, dr.Min.Y+int(dy))
|
||||
for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 {
|
||||
dxf := float64(dr.Min.X+int(dx)) + 0.5
|
||||
sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X
|
||||
sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y
|
||||
if !(image.Point{sx0, sy0}).In(sr) {
|
||||
continue
|
||||
}
|
||||
dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr) >> 8)
|
||||
dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg) >> 8)
|
||||
dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb) >> 8)
|
||||
dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa) >> 8)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
pr, pg, pb, pa := src.C.RGBA()
|
||||
pa1 := 0xffff - pa
|
||||
dstColorRGBA64 := &color.RGBA64{}
|
||||
dstColor := color.Color(dstColorRGBA64)
|
||||
|
||||
for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
|
||||
dyf := float64(dr.Min.Y+int(dy)) + 0.5
|
||||
for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ {
|
||||
dxf := float64(dr.Min.X+int(dx)) + 0.5
|
||||
sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X
|
||||
sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y
|
||||
if !(image.Point{sx0, sy0}).In(sr) {
|
||||
continue
|
||||
}
|
||||
qr, qg, qb, qa := dst.At(dr.Min.X+int(dx), dr.Min.Y+int(dy)).RGBA()
|
||||
dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr)
|
||||
dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg)
|
||||
dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb)
|
||||
dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa)
|
||||
dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case Src:
|
||||
switch dst := dst.(type) {
|
||||
case *image.RGBA:
|
||||
pr, pg, pb, pa := src.C.RGBA()
|
||||
pr8 := uint8(pr >> 8)
|
||||
pg8 := uint8(pg >> 8)
|
||||
pb8 := uint8(pb >> 8)
|
||||
pa8 := uint8(pa >> 8)
|
||||
|
||||
for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
|
||||
dyf := float64(dr.Min.Y+int(dy)) + 0.5
|
||||
d := dst.PixOffset(dr.Min.X+adr.Min.X, dr.Min.Y+int(dy))
|
||||
for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 {
|
||||
dxf := float64(dr.Min.X+int(dx)) + 0.5
|
||||
sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X
|
||||
sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y
|
||||
if !(image.Point{sx0, sy0}).In(sr) {
|
||||
continue
|
||||
}
|
||||
dst.Pix[d+0] = pr8
|
||||
dst.Pix[d+1] = pg8
|
||||
dst.Pix[d+2] = pb8
|
||||
dst.Pix[d+3] = pa8
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
pr, pg, pb, pa := src.C.RGBA()
|
||||
dstColorRGBA64 := &color.RGBA64{
|
||||
uint16(pr),
|
||||
uint16(pg),
|
||||
uint16(pb),
|
||||
uint16(pa),
|
||||
}
|
||||
dstColor := color.Color(dstColorRGBA64)
|
||||
|
||||
for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
|
||||
dyf := float64(dr.Min.Y+int(dy)) + 0.5
|
||||
for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ {
|
||||
dxf := float64(dr.Min.X+int(dx)) + 0.5
|
||||
sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X
|
||||
sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y
|
||||
if !(image.Point{sx0, sy0}).In(sr) {
|
||||
continue
|
||||
}
|
||||
dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func opaque(m image.Image) bool {
|
||||
o, ok := m.(interface {
|
||||
Opaque() bool
|
||||
})
|
||||
return ok && o.Opaque()
|
||||
}
|
|
@ -0,0 +1,731 @@
|
|||
// Copyright 2015 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 draw
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"math/rand"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/image/math/f64"
|
||||
|
||||
_ "image/jpeg"
|
||||
)
|
||||
|
||||
var genGoldenFiles = flag.Bool("gen_golden_files", false, "whether to generate the TestXxx golden files.")
|
||||
|
||||
var transformMatrix = func(scale, tx, ty float64) f64.Aff3 {
|
||||
const cos30, sin30 = 0.866025404, 0.5
|
||||
return f64.Aff3{
|
||||
+scale * cos30, -scale * sin30, tx,
|
||||
+scale * sin30, +scale * cos30, ty,
|
||||
}
|
||||
}
|
||||
|
||||
func encode(filename string, m image.Image) error {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Create: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
if err := png.Encode(f, m); err != nil {
|
||||
return fmt.Errorf("Encode: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// testInterp tests that interpolating the source image gives the exact
|
||||
// destination image. This is to ensure that any refactoring or optimization of
|
||||
// the interpolation code doesn't change the behavior. Changing the actual
|
||||
// algorithm or kernel used by any particular quality setting will obviously
|
||||
// change the resultant pixels. In such a case, use the gen_golden_files flag
|
||||
// to regenerate the golden files.
|
||||
func testInterp(t *testing.T, w int, h int, direction, prefix, suffix string) {
|
||||
f, err := os.Open("../testdata/" + prefix + suffix)
|
||||
if err != nil {
|
||||
t.Fatalf("Open: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
src, _, err := image.Decode(f)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode: %v", err)
|
||||
}
|
||||
|
||||
op, scale := Src, 3.75
|
||||
if prefix == "tux" {
|
||||
op, scale = Over, 0.125
|
||||
}
|
||||
green := image.NewUniform(color.RGBA{0x00, 0x22, 0x11, 0xff})
|
||||
|
||||
testCases := map[string]Interpolator{
|
||||
"nn": NearestNeighbor,
|
||||
"ab": ApproxBiLinear,
|
||||
"bl": BiLinear,
|
||||
"cr": CatmullRom,
|
||||
}
|
||||
for name, q := range testCases {
|
||||
goldenFilename := fmt.Sprintf("../testdata/%s-%s-%s.png", prefix, direction, name)
|
||||
|
||||
got := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
Copy(got, image.Point{}, green, got.Bounds(), Src, nil)
|
||||
if direction == "rotate" {
|
||||
q.Transform(got, transformMatrix(scale, 40, 10), src, src.Bounds(), op, nil)
|
||||
} else {
|
||||
q.Scale(got, got.Bounds(), src, src.Bounds(), op, nil)
|
||||
}
|
||||
|
||||
if *genGoldenFiles {
|
||||
if err := encode(goldenFilename, got); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
g, err := os.Open(goldenFilename)
|
||||
if err != nil {
|
||||
t.Errorf("Open: %v", err)
|
||||
continue
|
||||
}
|
||||
defer g.Close()
|
||||
wantRaw, err := png.Decode(g)
|
||||
if err != nil {
|
||||
t.Errorf("Decode: %v", err)
|
||||
continue
|
||||
}
|
||||
// convert wantRaw to RGBA.
|
||||
want, ok := wantRaw.(*image.RGBA)
|
||||
if !ok {
|
||||
b := wantRaw.Bounds()
|
||||
want = image.NewRGBA(b)
|
||||
Draw(want, b, wantRaw, b.Min, Src)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("%s: actual image differs from golden image", goldenFilename)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestScaleDown(t *testing.T) { testInterp(t, 100, 100, "down", "go-turns-two", "-280x360.jpeg") }
|
||||
func TestScaleUp(t *testing.T) { testInterp(t, 75, 100, "up", "go-turns-two", "-14x18.png") }
|
||||
func TestTformSrc(t *testing.T) { testInterp(t, 100, 100, "rotate", "go-turns-two", "-14x18.png") }
|
||||
func TestTformOver(t *testing.T) { testInterp(t, 100, 100, "rotate", "tux", ".png") }
|
||||
|
||||
// TestSimpleTransforms tests Scale and Transform calls that simplify to Copy
|
||||
// or Scale calls.
|
||||
func TestSimpleTransforms(t *testing.T) {
|
||||
f, err := os.Open("../testdata/testpattern.png") // A 100x100 image.
|
||||
if err != nil {
|
||||
t.Fatalf("Open: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
src, _, err := image.Decode(f)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode: %v", err)
|
||||
}
|
||||
|
||||
dst0 := image.NewRGBA(image.Rect(0, 0, 120, 150))
|
||||
dst1 := image.NewRGBA(image.Rect(0, 0, 120, 150))
|
||||
for _, op := range []string{"scale/copy", "tform/copy", "tform/scale"} {
|
||||
for _, epsilon := range []float64{0, 1e-50, 1e-1} {
|
||||
Copy(dst0, image.Point{}, image.Transparent, dst0.Bounds(), Src, nil)
|
||||
Copy(dst1, image.Point{}, image.Transparent, dst1.Bounds(), Src, nil)
|
||||
|
||||
switch op {
|
||||
case "scale/copy":
|
||||
dr := image.Rect(10, 30, 10+100, 30+100)
|
||||
if epsilon > 1e-10 {
|
||||
dr.Max.X++
|
||||
}
|
||||
Copy(dst0, image.Point{10, 30}, src, src.Bounds(), Src, nil)
|
||||
ApproxBiLinear.Scale(dst1, dr, src, src.Bounds(), Src, nil)
|
||||
case "tform/copy":
|
||||
Copy(dst0, image.Point{10, 30}, src, src.Bounds(), Src, nil)
|
||||
ApproxBiLinear.Transform(dst1, f64.Aff3{
|
||||
1, 0 + epsilon, 10,
|
||||
0, 1, 30,
|
||||
}, src, src.Bounds(), Src, nil)
|
||||
case "tform/scale":
|
||||
ApproxBiLinear.Scale(dst0, image.Rect(10, 50, 10+50, 50+50), src, src.Bounds(), Src, nil)
|
||||
ApproxBiLinear.Transform(dst1, f64.Aff3{
|
||||
0.5, 0.0 + epsilon, 10,
|
||||
0.0, 0.5, 50,
|
||||
}, src, src.Bounds(), Src, nil)
|
||||
}
|
||||
|
||||
differ := !bytes.Equal(dst0.Pix, dst1.Pix)
|
||||
if epsilon > 1e-10 {
|
||||
if !differ {
|
||||
t.Errorf("%s yielded same pixels, want different pixels: epsilon=%v", op, epsilon)
|
||||
}
|
||||
} else {
|
||||
if differ {
|
||||
t.Errorf("%s yielded different pixels, want same pixels: epsilon=%v", op, epsilon)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSimpleScaleCopy(b *testing.B) {
|
||||
dst := image.NewRGBA(image.Rect(0, 0, 640, 480))
|
||||
src := image.NewRGBA(image.Rect(0, 0, 400, 300))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ApproxBiLinear.Scale(dst, image.Rect(10, 20, 10+400, 20+300), src, src.Bounds(), Src, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSimpleTransformCopy(b *testing.B) {
|
||||
dst := image.NewRGBA(image.Rect(0, 0, 640, 480))
|
||||
src := image.NewRGBA(image.Rect(0, 0, 400, 300))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ApproxBiLinear.Transform(dst, f64.Aff3{
|
||||
1, 0, 10,
|
||||
0, 1, 20,
|
||||
}, src, src.Bounds(), Src, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSimpleTransformScale(b *testing.B) {
|
||||
dst := image.NewRGBA(image.Rect(0, 0, 640, 480))
|
||||
src := image.NewRGBA(image.Rect(0, 0, 400, 300))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ApproxBiLinear.Transform(dst, f64.Aff3{
|
||||
0.5, 0.0, 10,
|
||||
0.0, 0.5, 20,
|
||||
}, src, src.Bounds(), Src, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOps(t *testing.T) {
|
||||
blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff})
|
||||
testCases := map[Op]color.RGBA{
|
||||
Over: color.RGBA{0x7f, 0x00, 0x80, 0xff},
|
||||
Src: color.RGBA{0x7f, 0x00, 0x00, 0x7f},
|
||||
}
|
||||
for op, want := range testCases {
|
||||
dst := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
||||
Copy(dst, image.Point{}, blue, dst.Bounds(), Src, nil)
|
||||
|
||||
src := image.NewRGBA(image.Rect(0, 0, 1, 1))
|
||||
src.SetRGBA(0, 0, color.RGBA{0x7f, 0x00, 0x00, 0x7f})
|
||||
|
||||
NearestNeighbor.Scale(dst, dst.Bounds(), src, src.Bounds(), op, nil)
|
||||
|
||||
if got := dst.RGBAAt(0, 0); got != want {
|
||||
t.Errorf("op=%v: got %v, want %v", op, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestNegativeWeights tests that scaling by a kernel that produces negative
|
||||
// weights, such as the Catmull-Rom kernel, doesn't produce an invalid color
|
||||
// according to Go's alpha-premultiplied model.
|
||||
func TestNegativeWeights(t *testing.T) {
|
||||
check := func(m *image.RGBA) error {
|
||||
b := m.Bounds()
|
||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||
for x := b.Min.X; x < b.Max.X; x++ {
|
||||
if c := m.RGBAAt(x, y); c.R > c.A || c.G > c.A || c.B > c.A {
|
||||
return fmt.Errorf("invalid color.RGBA at (%d, %d): %v", x, y, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
src := image.NewRGBA(image.Rect(0, 0, 16, 16))
|
||||
for y := 0; y < 16; y++ {
|
||||
for x := 0; x < 16; x++ {
|
||||
a := y * 0x11
|
||||
src.Set(x, y, color.RGBA{
|
||||
R: uint8(x * 0x11 * a / 0xff),
|
||||
A: uint8(a),
|
||||
})
|
||||
}
|
||||
}
|
||||
if err := check(src); err != nil {
|
||||
t.Fatalf("src image: %v", err)
|
||||
}
|
||||
|
||||
dst := image.NewRGBA(image.Rect(0, 0, 32, 32))
|
||||
CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), Over, nil)
|
||||
if err := check(dst); err != nil {
|
||||
t.Fatalf("dst image: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func fillPix(r *rand.Rand, pixs ...[]byte) {
|
||||
for _, pix := range pixs {
|
||||
for i := range pix {
|
||||
pix[i] = uint8(r.Intn(256))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterpClipCommute(t *testing.T) {
|
||||
src := image.NewNRGBA(image.Rect(0, 0, 20, 20))
|
||||
fillPix(rand.New(rand.NewSource(0)), src.Pix)
|
||||
|
||||
outer := image.Rect(1, 1, 8, 5)
|
||||
inner := image.Rect(2, 3, 6, 5)
|
||||
qs := []Interpolator{
|
||||
NearestNeighbor,
|
||||
ApproxBiLinear,
|
||||
CatmullRom,
|
||||
}
|
||||
for _, transform := range []bool{false, true} {
|
||||
for _, q := range qs {
|
||||
dst0 := image.NewRGBA(image.Rect(1, 1, 10, 10))
|
||||
dst1 := image.NewRGBA(image.Rect(1, 1, 10, 10))
|
||||
for i := range dst0.Pix {
|
||||
dst0.Pix[i] = uint8(i / 4)
|
||||
dst1.Pix[i] = uint8(i / 4)
|
||||
}
|
||||
|
||||
var interp func(dst *image.RGBA)
|
||||
if transform {
|
||||
interp = func(dst *image.RGBA) {
|
||||
q.Transform(dst, transformMatrix(3.75, 2, 1), src, src.Bounds(), Over, nil)
|
||||
}
|
||||
} else {
|
||||
interp = func(dst *image.RGBA) {
|
||||
q.Scale(dst, outer, src, src.Bounds(), Over, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Interpolate then clip.
|
||||
interp(dst0)
|
||||
dst0 = dst0.SubImage(inner).(*image.RGBA)
|
||||
|
||||
// Clip then interpolate.
|
||||
dst1 = dst1.SubImage(inner).(*image.RGBA)
|
||||
interp(dst1)
|
||||
|
||||
loop:
|
||||
for y := inner.Min.Y; y < inner.Max.Y; y++ {
|
||||
for x := inner.Min.X; x < inner.Max.X; x++ {
|
||||
if c0, c1 := dst0.RGBAAt(x, y), dst1.RGBAAt(x, y); c0 != c1 {
|
||||
t.Errorf("q=%T: at (%d, %d): c0=%v, c1=%v", q, x, y, c0, c1)
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// translatedImage is an image m translated by t.
|
||||
type translatedImage struct {
|
||||
m image.Image
|
||||
t image.Point
|
||||
}
|
||||
|
||||
func (t *translatedImage) At(x, y int) color.Color { return t.m.At(x-t.t.X, y-t.t.Y) }
|
||||
func (t *translatedImage) Bounds() image.Rectangle { return t.m.Bounds().Add(t.t) }
|
||||
func (t *translatedImage) ColorModel() color.Model { return t.m.ColorModel() }
|
||||
|
||||
// TestSrcTranslationInvariance tests that Scale and Transform are invariant
|
||||
// under src translations. Specifically, when some source pixels are not in the
|
||||
// bottom-right quadrant of src coordinate space, we consistently round down,
|
||||
// not round towards zero.
|
||||
func TestSrcTranslationInvariance(t *testing.T) {
|
||||
f, err := os.Open("../testdata/testpattern.png")
|
||||
if err != nil {
|
||||
t.Fatalf("Open: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
src, _, err := image.Decode(f)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode: %v", err)
|
||||
}
|
||||
sr := image.Rect(2, 3, 16, 12)
|
||||
if !sr.In(src.Bounds()) {
|
||||
t.Fatalf("src bounds too small: got %v", src.Bounds())
|
||||
}
|
||||
qs := []Interpolator{
|
||||
NearestNeighbor,
|
||||
ApproxBiLinear,
|
||||
CatmullRom,
|
||||
}
|
||||
deltas := []image.Point{
|
||||
{+0, +0},
|
||||
{+0, +5},
|
||||
{+0, -5},
|
||||
{+5, +0},
|
||||
{-5, +0},
|
||||
{+8, +8},
|
||||
{+8, -8},
|
||||
{-8, +8},
|
||||
{-8, -8},
|
||||
}
|
||||
m00 := transformMatrix(3.75, 0, 0)
|
||||
|
||||
for _, transform := range []bool{false, true} {
|
||||
for _, q := range qs {
|
||||
want := image.NewRGBA(image.Rect(0, 0, 20, 20))
|
||||
if transform {
|
||||
q.Transform(want, m00, src, sr, Over, nil)
|
||||
} else {
|
||||
q.Scale(want, want.Bounds(), src, sr, Over, nil)
|
||||
}
|
||||
for _, delta := range deltas {
|
||||
tsrc := &translatedImage{src, delta}
|
||||
got := image.NewRGBA(image.Rect(0, 0, 20, 20))
|
||||
if transform {
|
||||
m := matMul(&m00, &f64.Aff3{
|
||||
1, 0, -float64(delta.X),
|
||||
0, 1, -float64(delta.Y),
|
||||
})
|
||||
q.Transform(got, m, tsrc, sr.Add(delta), Over, nil)
|
||||
} else {
|
||||
q.Scale(got, got.Bounds(), tsrc, sr.Add(delta), Over, nil)
|
||||
}
|
||||
if !bytes.Equal(got.Pix, want.Pix) {
|
||||
t.Errorf("pix differ for delta=%v, transform=%t, q=%T", delta, transform, q)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSrcMask(t *testing.T) {
|
||||
srcMask := image.NewRGBA(image.Rect(0, 0, 23, 1))
|
||||
srcMask.SetRGBA(19, 0, color.RGBA{0x00, 0x00, 0x00, 0x7f})
|
||||
srcMask.SetRGBA(20, 0, color.RGBA{0x00, 0x00, 0x00, 0xff})
|
||||
srcMask.SetRGBA(21, 0, color.RGBA{0x00, 0x00, 0x00, 0x3f})
|
||||
srcMask.SetRGBA(22, 0, color.RGBA{0x00, 0x00, 0x00, 0x00})
|
||||
red := image.NewUniform(color.RGBA{0xff, 0x00, 0x00, 0xff})
|
||||
blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff})
|
||||
dst := image.NewRGBA(image.Rect(0, 0, 6, 1))
|
||||
Copy(dst, image.Point{}, blue, dst.Bounds(), Src, nil)
|
||||
NearestNeighbor.Scale(dst, dst.Bounds(), red, image.Rect(0, 0, 3, 1), Over, &Options{
|
||||
SrcMask: srcMask,
|
||||
SrcMaskP: image.Point{20, 0},
|
||||
})
|
||||
got := [6]color.RGBA{
|
||||
dst.RGBAAt(0, 0),
|
||||
dst.RGBAAt(1, 0),
|
||||
dst.RGBAAt(2, 0),
|
||||
dst.RGBAAt(3, 0),
|
||||
dst.RGBAAt(4, 0),
|
||||
dst.RGBAAt(5, 0),
|
||||
}
|
||||
want := [6]color.RGBA{
|
||||
{0xff, 0x00, 0x00, 0xff},
|
||||
{0xff, 0x00, 0x00, 0xff},
|
||||
{0x3f, 0x00, 0xc0, 0xff},
|
||||
{0x3f, 0x00, 0xc0, 0xff},
|
||||
{0x00, 0x00, 0xff, 0xff},
|
||||
{0x00, 0x00, 0xff, 0xff},
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf("\ngot %v\nwant %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDstMask(t *testing.T) {
|
||||
dstMask := image.NewRGBA(image.Rect(0, 0, 23, 1))
|
||||
dstMask.SetRGBA(19, 0, color.RGBA{0x00, 0x00, 0x00, 0x7f})
|
||||
dstMask.SetRGBA(20, 0, color.RGBA{0x00, 0x00, 0x00, 0xff})
|
||||
dstMask.SetRGBA(21, 0, color.RGBA{0x00, 0x00, 0x00, 0x3f})
|
||||
dstMask.SetRGBA(22, 0, color.RGBA{0x00, 0x00, 0x00, 0x00})
|
||||
red := image.NewRGBA(image.Rect(0, 0, 1, 1))
|
||||
red.SetRGBA(0, 0, color.RGBA{0xff, 0x00, 0x00, 0xff})
|
||||
blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff})
|
||||
qs := []Interpolator{
|
||||
NearestNeighbor,
|
||||
ApproxBiLinear,
|
||||
CatmullRom,
|
||||
}
|
||||
for _, q := range qs {
|
||||
dst := image.NewRGBA(image.Rect(0, 0, 3, 1))
|
||||
Copy(dst, image.Point{}, blue, dst.Bounds(), Src, nil)
|
||||
q.Scale(dst, dst.Bounds(), red, red.Bounds(), Over, &Options{
|
||||
DstMask: dstMask,
|
||||
DstMaskP: image.Point{20, 0},
|
||||
})
|
||||
got := [3]color.RGBA{
|
||||
dst.RGBAAt(0, 0),
|
||||
dst.RGBAAt(1, 0),
|
||||
dst.RGBAAt(2, 0),
|
||||
}
|
||||
want := [3]color.RGBA{
|
||||
{0xff, 0x00, 0x00, 0xff},
|
||||
{0x3f, 0x00, 0xc0, 0xff},
|
||||
{0x00, 0x00, 0xff, 0xff},
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf("q=%T:\ngot %v\nwant %v", q, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRectDstMask(t *testing.T) {
|
||||
f, err := os.Open("../testdata/testpattern.png")
|
||||
if err != nil {
|
||||
t.Fatalf("Open: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
src, _, err := image.Decode(f)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode: %v", err)
|
||||
}
|
||||
m00 := transformMatrix(1, 0, 0)
|
||||
|
||||
bounds := image.Rect(0, 0, 50, 50)
|
||||
dstOutside := image.NewRGBA(bounds)
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||
dstOutside.SetRGBA(x, y, color.RGBA{uint8(5 * x), uint8(5 * y), 0x00, 0xff})
|
||||
}
|
||||
}
|
||||
|
||||
mk := func(q Transformer, dstMask image.Image, dstMaskP image.Point) *image.RGBA {
|
||||
m := image.NewRGBA(bounds)
|
||||
Copy(m, bounds.Min, dstOutside, bounds, Src, nil)
|
||||
q.Transform(m, m00, src, src.Bounds(), Over, &Options{
|
||||
DstMask: dstMask,
|
||||
DstMaskP: dstMaskP,
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
qs := []Interpolator{
|
||||
NearestNeighbor,
|
||||
ApproxBiLinear,
|
||||
CatmullRom,
|
||||
}
|
||||
dstMaskPs := []image.Point{
|
||||
{0, 0},
|
||||
{5, 7},
|
||||
{-3, 0},
|
||||
}
|
||||
rect := image.Rect(10, 10, 30, 40)
|
||||
for _, q := range qs {
|
||||
for _, dstMaskP := range dstMaskPs {
|
||||
dstInside := mk(q, nil, image.Point{})
|
||||
for _, wrap := range []bool{false, true} {
|
||||
// TODO: replace "rectImage(rect)" with "rect" once Go 1.5 is
|
||||
// released, where an image.Rectangle implements image.Image.
|
||||
dstMask := image.Image(rectImage(rect))
|
||||
if wrap {
|
||||
dstMask = srcWrapper{dstMask}
|
||||
}
|
||||
dst := mk(q, dstMask, dstMaskP)
|
||||
|
||||
nError := 0
|
||||
loop:
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||
which := dstOutside
|
||||
if (image.Point{x, y}).Add(dstMaskP).In(rect) {
|
||||
which = dstInside
|
||||
}
|
||||
if got, want := dst.RGBAAt(x, y), which.RGBAAt(x, y); got != want {
|
||||
if nError == 10 {
|
||||
t.Errorf("q=%T dmp=%v wrap=%v: ...and more errors", q, dstMaskP, wrap)
|
||||
break loop
|
||||
}
|
||||
nError++
|
||||
t.Errorf("q=%T dmp=%v wrap=%v: x=%3d y=%3d: got %v, want %v",
|
||||
q, dstMaskP, wrap, x, y, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: delete this wrapper type once Go 1.5 is released, where an
|
||||
// image.Rectangle implements image.Image.
|
||||
type rectImage image.Rectangle
|
||||
|
||||
func (r rectImage) ColorModel() color.Model { return color.Alpha16Model }
|
||||
func (r rectImage) Bounds() image.Rectangle { return image.Rectangle(r) }
|
||||
func (r rectImage) At(x, y int) color.Color {
|
||||
if (image.Point{x, y}).In(image.Rectangle(r)) {
|
||||
return color.Opaque
|
||||
}
|
||||
return color.Transparent
|
||||
}
|
||||
|
||||
// The fooWrapper types wrap the dst or src image to avoid triggering the
|
||||
// type-specific fast path implementations.
|
||||
type (
|
||||
dstWrapper struct{ Image }
|
||||
srcWrapper struct{ image.Image }
|
||||
)
|
||||
|
||||
func srcGray(boundsHint image.Rectangle) (image.Image, error) {
|
||||
m := image.NewGray(boundsHint)
|
||||
fillPix(rand.New(rand.NewSource(0)), m.Pix)
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func srcNRGBA(boundsHint image.Rectangle) (image.Image, error) {
|
||||
m := image.NewNRGBA(boundsHint)
|
||||
fillPix(rand.New(rand.NewSource(1)), m.Pix)
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func srcRGBA(boundsHint image.Rectangle) (image.Image, error) {
|
||||
m := image.NewRGBA(boundsHint)
|
||||
fillPix(rand.New(rand.NewSource(2)), m.Pix)
|
||||
// RGBA is alpha-premultiplied, so the R, G and B values should
|
||||
// be <= the A values.
|
||||
for i := 0; i < len(m.Pix); i += 4 {
|
||||
m.Pix[i+0] = uint8(uint32(m.Pix[i+0]) * uint32(m.Pix[i+3]) / 0xff)
|
||||
m.Pix[i+1] = uint8(uint32(m.Pix[i+1]) * uint32(m.Pix[i+3]) / 0xff)
|
||||
m.Pix[i+2] = uint8(uint32(m.Pix[i+2]) * uint32(m.Pix[i+3]) / 0xff)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func srcUnif(boundsHint image.Rectangle) (image.Image, error) {
|
||||
return image.NewUniform(color.RGBA64{0x1234, 0x5555, 0x9181, 0xbeef}), nil
|
||||
}
|
||||
|
||||
func srcYCbCr(boundsHint image.Rectangle) (image.Image, error) {
|
||||
m := image.NewYCbCr(boundsHint, image.YCbCrSubsampleRatio420)
|
||||
fillPix(rand.New(rand.NewSource(3)), m.Y, m.Cb, m.Cr)
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func srcLarge(boundsHint image.Rectangle) (image.Image, error) {
|
||||
// 3072 x 2304 is over 7 million pixels at 4:3, comparable to a
|
||||
// 2015 smart-phone camera's output.
|
||||
return srcYCbCr(image.Rect(0, 0, 3072, 2304))
|
||||
}
|
||||
|
||||
func srcTux(boundsHint image.Rectangle) (image.Image, error) {
|
||||
// tux.png is a 386 x 395 image.
|
||||
f, err := os.Open("../testdata/tux.png")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Open: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
src, err := png.Decode(f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Decode: %v", err)
|
||||
}
|
||||
return src, nil
|
||||
}
|
||||
|
||||
func benchScale(b *testing.B, w int, h int, op Op, srcf func(image.Rectangle) (image.Image, error), q Interpolator) {
|
||||
dst := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
src, err := srcf(image.Rect(0, 0, 1024, 768))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
dr, sr := dst.Bounds(), src.Bounds()
|
||||
scaler := Scaler(q)
|
||||
if n, ok := q.(interface {
|
||||
NewScaler(int, int, int, int) Scaler
|
||||
}); ok {
|
||||
scaler = n.NewScaler(dr.Dx(), dr.Dy(), sr.Dx(), sr.Dy())
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
scaler.Scale(dst, dr, src, sr, op, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func benchTform(b *testing.B, w int, h int, op Op, srcf func(image.Rectangle) (image.Image, error), q Interpolator) {
|
||||
dst := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
src, err := srcf(image.Rect(0, 0, 1024, 768))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
sr := src.Bounds()
|
||||
m := transformMatrix(3.75, 40, 10)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
q.Transform(dst, m, src, sr, op, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkScaleNNLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, NearestNeighbor) }
|
||||
func BenchmarkScaleABLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, ApproxBiLinear) }
|
||||
func BenchmarkScaleBLLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, BiLinear) }
|
||||
func BenchmarkScaleCRLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, CatmullRom) }
|
||||
|
||||
func BenchmarkScaleNNDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, NearestNeighbor) }
|
||||
func BenchmarkScaleABDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, ApproxBiLinear) }
|
||||
func BenchmarkScaleBLDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, BiLinear) }
|
||||
func BenchmarkScaleCRDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, CatmullRom) }
|
||||
|
||||
func BenchmarkScaleNNUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, NearestNeighbor) }
|
||||
func BenchmarkScaleABUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, ApproxBiLinear) }
|
||||
func BenchmarkScaleBLUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, BiLinear) }
|
||||
func BenchmarkScaleCRUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, CatmullRom) }
|
||||
|
||||
func BenchmarkScaleNNSrcRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcRGBA, NearestNeighbor) }
|
||||
func BenchmarkScaleNNSrcUnif(b *testing.B) { benchScale(b, 200, 150, Src, srcUnif, NearestNeighbor) }
|
||||
|
||||
func BenchmarkScaleNNOverRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcRGBA, NearestNeighbor) }
|
||||
func BenchmarkScaleNNOverUnif(b *testing.B) { benchScale(b, 200, 150, Over, srcUnif, NearestNeighbor) }
|
||||
|
||||
func BenchmarkTformNNSrcRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcRGBA, NearestNeighbor) }
|
||||
func BenchmarkTformNNSrcUnif(b *testing.B) { benchTform(b, 200, 150, Src, srcUnif, NearestNeighbor) }
|
||||
|
||||
func BenchmarkTformNNOverRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcRGBA, NearestNeighbor) }
|
||||
func BenchmarkTformNNOverUnif(b *testing.B) { benchTform(b, 200, 150, Over, srcUnif, NearestNeighbor) }
|
||||
|
||||
func BenchmarkScaleABSrcGray(b *testing.B) { benchScale(b, 200, 150, Src, srcGray, ApproxBiLinear) }
|
||||
func BenchmarkScaleABSrcNRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcNRGBA, ApproxBiLinear) }
|
||||
func BenchmarkScaleABSrcRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcRGBA, ApproxBiLinear) }
|
||||
func BenchmarkScaleABSrcYCbCr(b *testing.B) { benchScale(b, 200, 150, Src, srcYCbCr, ApproxBiLinear) }
|
||||
|
||||
func BenchmarkScaleABOverGray(b *testing.B) { benchScale(b, 200, 150, Over, srcGray, ApproxBiLinear) }
|
||||
func BenchmarkScaleABOverNRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcNRGBA, ApproxBiLinear) }
|
||||
func BenchmarkScaleABOverRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcRGBA, ApproxBiLinear) }
|
||||
func BenchmarkScaleABOverYCbCr(b *testing.B) { benchScale(b, 200, 150, Over, srcYCbCr, ApproxBiLinear) }
|
||||
|
||||
func BenchmarkTformABSrcGray(b *testing.B) { benchTform(b, 200, 150, Src, srcGray, ApproxBiLinear) }
|
||||
func BenchmarkTformABSrcNRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcNRGBA, ApproxBiLinear) }
|
||||
func BenchmarkTformABSrcRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcRGBA, ApproxBiLinear) }
|
||||
func BenchmarkTformABSrcYCbCr(b *testing.B) { benchTform(b, 200, 150, Src, srcYCbCr, ApproxBiLinear) }
|
||||
|
||||
func BenchmarkTformABOverGray(b *testing.B) { benchTform(b, 200, 150, Over, srcGray, ApproxBiLinear) }
|
||||
func BenchmarkTformABOverNRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcNRGBA, ApproxBiLinear) }
|
||||
func BenchmarkTformABOverRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcRGBA, ApproxBiLinear) }
|
||||
func BenchmarkTformABOverYCbCr(b *testing.B) { benchTform(b, 200, 150, Over, srcYCbCr, ApproxBiLinear) }
|
||||
|
||||
func BenchmarkScaleCRSrcGray(b *testing.B) { benchScale(b, 200, 150, Src, srcGray, CatmullRom) }
|
||||
func BenchmarkScaleCRSrcNRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcNRGBA, CatmullRom) }
|
||||
func BenchmarkScaleCRSrcRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcRGBA, CatmullRom) }
|
||||
func BenchmarkScaleCRSrcYCbCr(b *testing.B) { benchScale(b, 200, 150, Src, srcYCbCr, CatmullRom) }
|
||||
|
||||
func BenchmarkScaleCROverGray(b *testing.B) { benchScale(b, 200, 150, Over, srcGray, CatmullRom) }
|
||||
func BenchmarkScaleCROverNRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcNRGBA, CatmullRom) }
|
||||
func BenchmarkScaleCROverRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcRGBA, CatmullRom) }
|
||||
func BenchmarkScaleCROverYCbCr(b *testing.B) { benchScale(b, 200, 150, Over, srcYCbCr, CatmullRom) }
|
||||
|
||||
func BenchmarkTformCRSrcGray(b *testing.B) { benchTform(b, 200, 150, Src, srcGray, CatmullRom) }
|
||||
func BenchmarkTformCRSrcNRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcNRGBA, CatmullRom) }
|
||||
func BenchmarkTformCRSrcRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcRGBA, CatmullRom) }
|
||||
func BenchmarkTformCRSrcYCbCr(b *testing.B) { benchTform(b, 200, 150, Src, srcYCbCr, CatmullRom) }
|
||||
|
||||
func BenchmarkTformCROverGray(b *testing.B) { benchTform(b, 200, 150, Over, srcGray, CatmullRom) }
|
||||
func BenchmarkTformCROverNRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcNRGBA, CatmullRom) }
|
||||
func BenchmarkTformCROverRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcRGBA, CatmullRom) }
|
||||
func BenchmarkTformCROverYCbCr(b *testing.B) { benchTform(b, 200, 150, Over, srcYCbCr, CatmullRom) }
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright 2015 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.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package draw
|
||||
|
||||
// This file contains tests that depend on the exact behavior of the
|
||||
// image/color package in the standard library. The color conversion formula
|
||||
// from YCbCr to RGBA changed between Go 1.4 and Go 1.5, so this file's tests
|
||||
// are only enabled for Go 1.5 and above.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/color"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestFastPaths tests that the fast path implementations produce identical
|
||||
// results to the generic implementation.
|
||||
func TestFastPaths(t *testing.T) {
|
||||
drs := []image.Rectangle{
|
||||
image.Rect(0, 0, 10, 10), // The dst bounds.
|
||||
image.Rect(3, 4, 8, 6), // A strict subset of the dst bounds.
|
||||
image.Rect(-3, -5, 2, 4), // Partial out-of-bounds #0.
|
||||
image.Rect(4, -2, 6, 12), // Partial out-of-bounds #1.
|
||||
image.Rect(12, 14, 23, 45), // Complete out-of-bounds.
|
||||
image.Rect(5, 5, 5, 5), // Empty.
|
||||
}
|
||||
srs := []image.Rectangle{
|
||||
image.Rect(0, 0, 12, 9), // The src bounds.
|
||||
image.Rect(2, 2, 10, 8), // A strict subset of the src bounds.
|
||||
image.Rect(10, 5, 20, 20), // Partial out-of-bounds #0.
|
||||
image.Rect(-40, 0, 40, 8), // Partial out-of-bounds #1.
|
||||
image.Rect(-8, -8, -4, -4), // Complete out-of-bounds.
|
||||
image.Rect(5, 5, 5, 5), // Empty.
|
||||
}
|
||||
srcfs := []func(image.Rectangle) (image.Image, error){
|
||||
srcGray,
|
||||
srcNRGBA,
|
||||
srcRGBA,
|
||||
srcUnif,
|
||||
srcYCbCr,
|
||||
}
|
||||
var srcs []image.Image
|
||||
for _, srcf := range srcfs {
|
||||
src, err := srcf(srs[0])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
srcs = append(srcs, src)
|
||||
}
|
||||
qs := []Interpolator{
|
||||
NearestNeighbor,
|
||||
ApproxBiLinear,
|
||||
CatmullRom,
|
||||
}
|
||||
ops := []Op{
|
||||
Over,
|
||||
Src,
|
||||
}
|
||||
blue := image.NewUniform(color.RGBA{0x11, 0x22, 0x44, 0x7f})
|
||||
|
||||
for _, dr := range drs {
|
||||
for _, src := range srcs {
|
||||
for _, sr := range srs {
|
||||
for _, transform := range []bool{false, true} {
|
||||
for _, q := range qs {
|
||||
for _, op := range ops {
|
||||
dst0 := image.NewRGBA(drs[0])
|
||||
dst1 := image.NewRGBA(drs[0])
|
||||
Draw(dst0, dst0.Bounds(), blue, image.Point{}, Src)
|
||||
Draw(dstWrapper{dst1}, dst1.Bounds(), srcWrapper{blue}, image.Point{}, Src)
|
||||
|
||||
if transform {
|
||||
m := transformMatrix(3.75, 2, 1)
|
||||
q.Transform(dst0, m, src, sr, op, nil)
|
||||
q.Transform(dstWrapper{dst1}, m, srcWrapper{src}, sr, op, nil)
|
||||
} else {
|
||||
q.Scale(dst0, dr, src, sr, op, nil)
|
||||
q.Scale(dstWrapper{dst1}, dr, srcWrapper{src}, sr, op, nil)
|
||||
}
|
||||
|
||||
if !bytes.Equal(dst0.Pix, dst1.Pix) {
|
||||
t.Errorf("pix differ for dr=%v, src=%T, sr=%v, transform=%t, q=%T",
|
||||
dr, src, sr, transform, q)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright 2015 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.
|
||||
|
||||
// +build ignore
|
||||
//
|
||||
// This build tag means that "go install golang.org/x/image/..." doesn't
|
||||
// install this example program. Use "go run main.go" to run it.
|
||||
|
||||
// Font is a basic example of using fonts.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/png"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/plan9font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
var (
|
||||
fontFlag = flag.String("font", "",
|
||||
`filename of the Plan 9 font or subfont file, such as "lucsans/unicode.8.font" or "lucsans/lsr.14"`)
|
||||
firstRuneFlag = flag.Int("firstrune", 0, "the Unicode code point of the first rune in the subfont file")
|
||||
)
|
||||
|
||||
func pt(p fixed.Point26_6) image.Point {
|
||||
return image.Point{
|
||||
X: int(p.X+32) >> 6,
|
||||
Y: int(p.Y+32) >> 6,
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// TODO: mmap the files.
|
||||
if *fontFlag == "" {
|
||||
flag.Usage()
|
||||
log.Fatal("no font specified")
|
||||
}
|
||||
var face font.Face
|
||||
if strings.HasSuffix(*fontFlag, ".font") {
|
||||
fontData, err := ioutil.ReadFile(*fontFlag)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
dir := filepath.Dir(*fontFlag)
|
||||
face, err = plan9font.ParseFont(fontData, func(name string) ([]byte, error) {
|
||||
return ioutil.ReadFile(filepath.Join(dir, filepath.FromSlash(name)))
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
fontData, err := ioutil.ReadFile(*fontFlag)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
face, err = plan9font.ParseSubfont(fontData, rune(*firstRuneFlag))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
dst := image.NewRGBA(image.Rect(0, 0, 800, 300))
|
||||
draw.Draw(dst, dst.Bounds(), image.Black, image.Point{}, draw.Src)
|
||||
|
||||
d := &font.Drawer{
|
||||
Dst: dst,
|
||||
Src: image.White,
|
||||
Face: face,
|
||||
}
|
||||
ss := []string{
|
||||
"The quick brown fox jumps over the lazy dog.",
|
||||
"Hello, 世界.",
|
||||
"U+FFFD is \ufffd.",
|
||||
}
|
||||
for i, s := range ss {
|
||||
d.Dot = fixed.P(20, 100*i+80)
|
||||
dot0 := pt(d.Dot)
|
||||
d.DrawString(s)
|
||||
dot1 := pt(d.Dot)
|
||||
dst.SetRGBA(dot0.X, dot0.Y, color.RGBA{0xff, 0x00, 0x00, 0xff})
|
||||
dst.SetRGBA(dot1.X, dot1.Y, color.RGBA{0x00, 0x00, 0xff, 0xff})
|
||||
}
|
||||
|
||||
out, err := os.Create("out.png")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer out.Close()
|
||||
if err := png.Encode(out, dst); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
// Copyright 2015 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.
|
||||
|
||||
//go:generate go run gen.go
|
||||
|
||||
// Package basicfont provides fixed-size font faces.
|
||||
package basicfont // import "golang.org/x/image/font/basicfont"
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
// Range maps a contiguous range of runes to vertically adjacent sub-images of
|
||||
// a Face's Mask image. The rune range is inclusive on the low end and
|
||||
// exclusive on the high end.
|
||||
//
|
||||
// If Low <= r && r < High, then the rune r is mapped to the sub-image of
|
||||
// Face.Mask whose bounds are image.Rect(0, y, Face.Width, y+Face.Height),
|
||||
// where y equals (int(r-Low) + Offset) * Face.Height.
|
||||
type Range struct {
|
||||
Low, High rune
|
||||
Offset int
|
||||
}
|
||||
|
||||
// Face7x13 is a Face derived from the public domain X11 misc-fixed font files.
|
||||
//
|
||||
// At the moment, it holds the printable characters in ASCII starting with
|
||||
// space, and the Unicode replacement character U+FFFD.
|
||||
//
|
||||
// Its data is entirely self-contained and does not require loading from
|
||||
// separate files.
|
||||
var Face7x13 = &Face{
|
||||
Advance: 7,
|
||||
Width: 6,
|
||||
Height: 13,
|
||||
Ascent: 11,
|
||||
Mask: mask7x13,
|
||||
Ranges: []Range{
|
||||
{'\u0020', '\u007f', 0},
|
||||
{'\ufffd', '\ufffe', 95},
|
||||
},
|
||||
}
|
||||
|
||||
// Face is a basic font face whose glyphs all have the same metrics.
|
||||
//
|
||||
// It is safe to use concurrently.
|
||||
type Face struct {
|
||||
// Advance is the glyph advance, in pixels.
|
||||
Advance int
|
||||
// Width is the glyph width, in pixels.
|
||||
Width int
|
||||
// Height is the glyph height, in pixels.
|
||||
Height int
|
||||
// Ascent is the glyph ascent, in pixels.
|
||||
Ascent int
|
||||
|
||||
// TODO: do we also need Top and Left fields?
|
||||
|
||||
// Mask contains all of the glyph masks. Its width is typically the Face's
|
||||
// Width, and its height a multiple of the Face's Height.
|
||||
Mask image.Image
|
||||
// Ranges map runes to sub-images of Mask. The rune ranges must not
|
||||
// overlap, and must be in increasing rune order.
|
||||
Ranges []Range
|
||||
}
|
||||
|
||||
func (f *Face) Close() error { return nil }
|
||||
func (f *Face) Kern(r0, r1 rune) fixed.Int26_6 { return 0 }
|
||||
|
||||
func (f *Face) Glyph(dot fixed.Point26_6, r rune) (
|
||||
dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
|
||||
|
||||
loop:
|
||||
for _, rr := range [2]rune{r, '\ufffd'} {
|
||||
for _, rng := range f.Ranges {
|
||||
if rr < rng.Low || rng.High <= rr {
|
||||
continue
|
||||
}
|
||||
maskp.Y = (int(rr-rng.Low) + rng.Offset) * f.Height
|
||||
ok = true
|
||||
break loop
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return image.Rectangle{}, nil, image.Point{}, 0, false
|
||||
}
|
||||
|
||||
minX := int(dot.X+32) >> 6
|
||||
minY := int(dot.Y+32)>>6 - f.Ascent
|
||||
dr = image.Rectangle{
|
||||
Min: image.Point{
|
||||
X: minX,
|
||||
Y: minY,
|
||||
},
|
||||
Max: image.Point{
|
||||
X: minX + f.Width,
|
||||
Y: minY + f.Height,
|
||||
},
|
||||
}
|
||||
|
||||
return dr, f.Mask, maskp, fixed.I(f.Advance), true
|
||||
}
|
||||
|
||||
func (f *Face) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
|
||||
return fixed.R(0, -f.Ascent, f.Width, -f.Ascent+f.Height), fixed.I(f.Advance), true
|
||||
}
|
||||
|
||||
func (f *Face) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
|
||||
return fixed.I(f.Advance), true
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
// Copyright 2015 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.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// This program generates data.go.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"image"
|
||||
"image/draw"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/plan9font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// nGlyphs is the number of glyphs to generate: 95 characters in the range
|
||||
// [0x20, 0x7e], plus the replacement character.
|
||||
const nGlyphs = 95 + 1
|
||||
// The particular font (unicode.7x13.font) leaves the right-most column
|
||||
// empty in its ASCII glyphs. We don't have to include that column in the
|
||||
// generated glyphs, so we subtract one off the effective width.
|
||||
const width, height, ascent = 7 - 1, 13, 11
|
||||
|
||||
readFile := func(name string) ([]byte, error) {
|
||||
return ioutil.ReadFile(filepath.FromSlash(path.Join("../testdata/fixed", name)))
|
||||
}
|
||||
fontData, err := readFile("unicode.7x13.font")
|
||||
if err != nil {
|
||||
log.Fatalf("readFile: %v", err)
|
||||
}
|
||||
face, err := plan9font.ParseFont(fontData, readFile)
|
||||
if err != nil {
|
||||
log.Fatalf("plan9font.ParseFont: %v", err)
|
||||
}
|
||||
|
||||
dst := image.NewRGBA(image.Rect(0, 0, width, nGlyphs*height))
|
||||
draw.Draw(dst, dst.Bounds(), image.Black, image.Point{}, draw.Src)
|
||||
d := &font.Drawer{
|
||||
Dst: dst,
|
||||
Src: image.White,
|
||||
Face: face,
|
||||
}
|
||||
for i := 0; i < nGlyphs; i++ {
|
||||
r := '\ufffd'
|
||||
if i < nGlyphs-1 {
|
||||
r = 0x20 + rune(i)
|
||||
}
|
||||
d.Dot = fixed.P(0, height*i+ascent)
|
||||
d.DrawString(string(r))
|
||||
}
|
||||
|
||||
w := bytes.NewBuffer(nil)
|
||||
w.WriteString(preamble)
|
||||
fmt.Fprintf(w, "// mask7x13 contains %d %d×%d glyphs in %d Pix bytes.\n", nGlyphs, width, height, nGlyphs*width*height)
|
||||
fmt.Fprintf(w, "var mask7x13 = &image.Alpha{\n")
|
||||
fmt.Fprintf(w, " Stride: %d,\n", width)
|
||||
fmt.Fprintf(w, " Rect: image.Rectangle{Max: image.Point{%d, %d*%d}},\n", width, nGlyphs, height)
|
||||
fmt.Fprintf(w, " Pix: []byte{\n")
|
||||
b := dst.Bounds()
|
||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||
if y%height == 0 {
|
||||
if y != 0 {
|
||||
w.WriteByte('\n')
|
||||
}
|
||||
i := y / height
|
||||
if i < nGlyphs-1 {
|
||||
i += 0x20
|
||||
fmt.Fprintf(w, "// %#2x %q\n", i, rune(i))
|
||||
} else {
|
||||
fmt.Fprintf(w, "// U+FFFD REPLACEMENT CHARACTER\n")
|
||||
}
|
||||
}
|
||||
|
||||
for x := b.Min.X; x < b.Max.X; x++ {
|
||||
if dst.RGBAAt(x, y).R > 0 {
|
||||
w.WriteString("0xff,")
|
||||
} else {
|
||||
w.WriteString("0x00,")
|
||||
}
|
||||
}
|
||||
w.WriteByte('\n')
|
||||
}
|
||||
w.WriteString("},\n}\n")
|
||||
|
||||
fmted, err := format.Source(w.Bytes())
|
||||
if err != nil {
|
||||
log.Fatalf("format.Source: %v", err)
|
||||
}
|
||||
if err := ioutil.WriteFile("data.go", fmted, 0644); err != nil {
|
||||
log.Fatalf("ioutil.WriteFile: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
const preamble = `// generated by go generate; DO NOT EDIT.
|
||||
|
||||
package basicfont
|
||||
|
||||
// This data is derived from files in the font/fixed directory of the Plan 9
|
||||
// Port source code (https://github.com/9fans/plan9port) which were originally
|
||||
// based on the public domain X11 misc-fixed font files.
|
||||
|
||||
import "image"
|
||||
|
||||
`
|
|
@ -0,0 +1,202 @@
|
|||
// Copyright 2015 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 font defines an interface for font faces, for drawing text on an
|
||||
// image.
|
||||
//
|
||||
// Other packages provide font face implementations. For example, a truetype
|
||||
// package would provide one based on .ttf font files.
|
||||
package font // import "golang.org/x/image/font"
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/draw"
|
||||
"io"
|
||||
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
// TODO: who is responsible for caches (glyph images, glyph indices, kerns)?
|
||||
// The Drawer or the Face?
|
||||
|
||||
// Face is a font face. Its glyphs are often derived from a font file, such as
|
||||
// "Comic_Sans_MS.ttf", but a face has a specific size, style, weight and
|
||||
// hinting. For example, the 12pt and 18pt versions of Comic Sans are two
|
||||
// different faces, even if derived from the same font file.
|
||||
//
|
||||
// A Face is not safe for concurrent use by multiple goroutines, as its methods
|
||||
// may re-use implementation-specific caches and mask image buffers.
|
||||
//
|
||||
// To create a Face, look to other packages that implement specific font file
|
||||
// formats.
|
||||
type Face interface {
|
||||
io.Closer
|
||||
|
||||
// Glyph returns the draw.DrawMask parameters (dr, mask, maskp) to draw r's
|
||||
// glyph at the sub-pixel destination location dot, and that glyph's
|
||||
// advance width.
|
||||
//
|
||||
// It returns !ok if the face does not contain a glyph for r.
|
||||
//
|
||||
// The contents of the mask image returned by one Glyph call may change
|
||||
// after the next Glyph call. Callers that want to cache the mask must make
|
||||
// a copy.
|
||||
Glyph(dot fixed.Point26_6, r rune) (
|
||||
dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool)
|
||||
|
||||
// GlyphBounds returns the bounding box of r's glyph, drawn at a dot equal
|
||||
// to the origin, and that glyph's advance width.
|
||||
//
|
||||
// It returns !ok if the face does not contain a glyph for r.
|
||||
//
|
||||
// The glyph's ascent and descent equal -bounds.Min.Y and +bounds.Max.Y. A
|
||||
// visual depiction of what these metrics are is at
|
||||
// https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyph_metrics_2x.png
|
||||
GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool)
|
||||
|
||||
// GlyphAdvance returns the advance width of r's glyph.
|
||||
//
|
||||
// It returns !ok if the face does not contain a glyph for r.
|
||||
GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool)
|
||||
|
||||
// Kern returns the horizontal adjustment for the kerning pair (r0, r1). A
|
||||
// positive kern means to move the glyphs further apart.
|
||||
Kern(r0, r1 rune) fixed.Int26_6
|
||||
|
||||
// TODO: per-font Metrics.
|
||||
// TODO: ColoredGlyph for various emoji?
|
||||
// TODO: Ligatures? Shaping?
|
||||
}
|
||||
|
||||
// TODO: Drawer.Layout or Drawer.Measure methods to measure text without
|
||||
// drawing?
|
||||
|
||||
// Drawer draws text on a destination image.
|
||||
//
|
||||
// A Drawer is not safe for concurrent use by multiple goroutines, since its
|
||||
// Face is not.
|
||||
type Drawer struct {
|
||||
// Dst is the destination image.
|
||||
Dst draw.Image
|
||||
// Src is the source image.
|
||||
Src image.Image
|
||||
// Face provides the glyph mask images.
|
||||
Face Face
|
||||
// Dot is the baseline location to draw the next glyph. The majority of the
|
||||
// affected pixels will be above and to the right of the dot, but some may
|
||||
// be below or to the left. For example, drawing a 'j' in an italic face
|
||||
// may affect pixels below and to the left of the dot.
|
||||
Dot fixed.Point26_6
|
||||
|
||||
// TODO: Clip image.Image?
|
||||
// TODO: SrcP image.Point for Src images other than *image.Uniform? How
|
||||
// does it get updated during DrawString?
|
||||
}
|
||||
|
||||
// TODO: should DrawString return the last rune drawn, so the next DrawString
|
||||
// call can kern beforehand? Or should that be the responsibility of the caller
|
||||
// if they really want to do that, since they have to explicitly shift d.Dot
|
||||
// anyway?
|
||||
//
|
||||
// In general, we'd have a DrawBytes([]byte) and DrawRuneReader(io.RuneReader)
|
||||
// and the last case can't assume that you can rewind the stream.
|
||||
//
|
||||
// TODO: how does this work with line breaking: drawing text up until a
|
||||
// vertical line? Should DrawString return the number of runes drawn?
|
||||
|
||||
// DrawString draws s at the dot and advances the dot's location.
|
||||
func (d *Drawer) DrawString(s string) {
|
||||
var prevC rune
|
||||
for i, c := range s {
|
||||
if i != 0 {
|
||||
d.Dot.X += d.Face.Kern(prevC, c)
|
||||
}
|
||||
dr, mask, maskp, advance, ok := d.Face.Glyph(d.Dot, c)
|
||||
if !ok {
|
||||
// TODO: is falling back on the U+FFFD glyph the responsibility of
|
||||
// the Drawer or the Face?
|
||||
// TODO: set prevC = '\ufffd'?
|
||||
continue
|
||||
}
|
||||
draw.DrawMask(d.Dst, dr, d.Src, image.Point{}, mask, maskp, draw.Over)
|
||||
d.Dot.X += advance
|
||||
prevC = c
|
||||
}
|
||||
}
|
||||
|
||||
// MeasureString returns how far dot would advance by drawing s.
|
||||
func (d *Drawer) MeasureString(s string) (advance fixed.Int26_6) {
|
||||
var prevC rune
|
||||
for i, c := range s {
|
||||
if i != 0 {
|
||||
advance += d.Face.Kern(prevC, c)
|
||||
}
|
||||
a, ok := d.Face.GlyphAdvance(c)
|
||||
if !ok {
|
||||
// TODO: is falling back on the U+FFFD glyph the responsibility of
|
||||
// the Drawer or the Face?
|
||||
// TODO: set prevC = '\ufffd'?
|
||||
continue
|
||||
}
|
||||
advance += a
|
||||
prevC = c
|
||||
}
|
||||
return advance
|
||||
}
|
||||
|
||||
// Hinting selects how to quantize a vector font's glyph nodes.
|
||||
//
|
||||
// Not all fonts support hinting.
|
||||
type Hinting int
|
||||
|
||||
const (
|
||||
HintingNone Hinting = iota
|
||||
HintingVertical
|
||||
HintingFull
|
||||
)
|
||||
|
||||
// Stretch selects a normal, condensed, or expanded face.
|
||||
//
|
||||
// Not all fonts support stretches.
|
||||
type Stretch int
|
||||
|
||||
const (
|
||||
StretchUltraCondensed Stretch = -4
|
||||
StretchExtraCondensed Stretch = -3
|
||||
StretchCondensed Stretch = -2
|
||||
StretchSemiCondensed Stretch = -1
|
||||
StretchNormal Stretch = +0
|
||||
StretchSemiExpanded Stretch = +1
|
||||
StretchExpanded Stretch = +2
|
||||
StretchExtraExpanded Stretch = +3
|
||||
StretchUltraExpanded Stretch = +4
|
||||
)
|
||||
|
||||
// Style selects a normal, italic, or oblique face.
|
||||
//
|
||||
// Not all fonts support styles.
|
||||
type Style int
|
||||
|
||||
const (
|
||||
StyleNormal Style = iota
|
||||
StyleItalic
|
||||
StyleOblique
|
||||
)
|
||||
|
||||
// Weight selects a normal, light or bold face.
|
||||
//
|
||||
// Not all fonts support weights.
|
||||
type Weight int
|
||||
|
||||
const (
|
||||
WeightThin Weight = 100
|
||||
WeightExtraLight Weight = 200
|
||||
WeightLight Weight = 300
|
||||
WeightNormal Weight = 400
|
||||
WeightMedium Weight = 500
|
||||
WeightSemiBold Weight = 600
|
||||
WeightBold Weight = 700
|
||||
WeightExtraBold Weight = 800
|
||||
WeightBlack Weight = 900
|
||||
)
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright 2015 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 plan9font_test
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/draw"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/plan9font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
func ExampleParseFont() {
|
||||
readFile := func(name string) ([]byte, error) {
|
||||
return ioutil.ReadFile(filepath.FromSlash(path.Join("../testdata/fixed", name)))
|
||||
}
|
||||
fontData, err := readFile("unicode.7x13.font")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
face, err := plan9font.ParseFont(fontData, readFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// TODO: derive the ascent from the face's metrics.
|
||||
const ascent = 11
|
||||
|
||||
dst := image.NewRGBA(image.Rect(0, 0, 4*7, 13))
|
||||
draw.Draw(dst, dst.Bounds(), image.Black, image.Point{}, draw.Src)
|
||||
d := &font.Drawer{
|
||||
Dst: dst,
|
||||
Src: image.White,
|
||||
Face: face,
|
||||
Dot: fixed.P(0, ascent),
|
||||
}
|
||||
// Draw:
|
||||
// - U+0053 LATIN CAPITAL LETTER S
|
||||
// - U+03A3 GREEK CAPITAL LETTER SIGMA
|
||||
// - U+222B INTEGRAL
|
||||
// - U+3055 HIRAGANA LETTER SA
|
||||
// The testdata does not contain the CJK subfont files, so U+3055 HIRAGANA
|
||||
// LETTER SA (さ) should be rendered as U+FFFD REPLACEMENT CHARACTER (<28>).
|
||||
//
|
||||
// The missing subfont file will trigger an "open
|
||||
// ../testdata/shinonome/k12.3000: no such file or directory" log message.
|
||||
// This is expected and can be ignored.
|
||||
d.DrawString("SΣ∫さ")
|
||||
|
||||
// Convert the dst image to ASCII art.
|
||||
var out []byte
|
||||
b := dst.Bounds()
|
||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||
out = append(out, '0'+byte(y%10), ' ')
|
||||
for x := b.Min.X; x < b.Max.X; x++ {
|
||||
if dst.RGBAAt(x, y).R > 0 {
|
||||
out = append(out, 'X')
|
||||
} else {
|
||||
out = append(out, '.')
|
||||
}
|
||||
}
|
||||
// Highlight the last row before the baseline. Glyphs like 'S' without
|
||||
// descenders should not affect any pixels whose Y coordinate is >= the
|
||||
// baseline.
|
||||
if y == ascent-1 {
|
||||
out = append(out, '_')
|
||||
}
|
||||
out = append(out, '\n')
|
||||
}
|
||||
os.Stdout.Write(out)
|
||||
|
||||
// Output:
|
||||
// 0 ..................X.........
|
||||
// 1 .................X.X........
|
||||
// 2 .XXXX..XXXXXX....X.....XXX..
|
||||
// 3 X....X.X.........X....XX.XX.
|
||||
// 4 X.......X........X....X.X.X.
|
||||
// 5 X........X.......X....XXX.X.
|
||||
// 6 .XXXX.....X......X....XX.XX.
|
||||
// 7 .....X...X.......X....XX.XX.
|
||||
// 8 .....X..X........X....XXXXX.
|
||||
// 9 X....X.X.........X....XX.XX.
|
||||
// 0 .XXXX..XXXXXX....X.....XXX.._
|
||||
// 1 ...............X.X..........
|
||||
// 2 ................X...........
|
||||
}
|
|
@ -0,0 +1,556 @@
|
|||
// Copyright 2015 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 plan9font implements font faces for the Plan 9 font and subfont file
|
||||
// formats. These formats are described at
|
||||
// http://plan9.bell-labs.com/magic/man2html/6/font
|
||||
package plan9font // import "golang.org/x/image/font/plan9font"
|
||||
|
||||
// TODO: have a subface use an *image.Alpha instead of plan9Image implementing
|
||||
// the image.Image interface? The image/draw code has a fast path for
|
||||
// *image.Alpha masks.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
// fontchar describes one character glyph in a subfont.
|
||||
//
|
||||
// For more detail, look for "struct Fontchar" in
|
||||
// http://plan9.bell-labs.com/magic/man2html/2/cachechars
|
||||
type fontchar struct {
|
||||
x uint32 // X position in the image holding the glyphs.
|
||||
top uint8 // First non-zero scan line.
|
||||
bottom uint8 // Last non-zero scan line.
|
||||
left int8 // Offset of baseline.
|
||||
width uint8 // Width of baseline.
|
||||
}
|
||||
|
||||
func parseFontchars(p []byte) []fontchar {
|
||||
fc := make([]fontchar, len(p)/6)
|
||||
for i := range fc {
|
||||
fc[i] = fontchar{
|
||||
x: uint32(p[0]) | uint32(p[1])<<8,
|
||||
top: uint8(p[2]),
|
||||
bottom: uint8(p[3]),
|
||||
left: int8(p[4]),
|
||||
width: uint8(p[5]),
|
||||
}
|
||||
p = p[6:]
|
||||
}
|
||||
return fc
|
||||
}
|
||||
|
||||
// subface implements font.Face for a Plan 9 subfont.
|
||||
type subface struct {
|
||||
firstRune rune // First rune in the subfont.
|
||||
n int // Number of characters in the subfont.
|
||||
height int // Inter-line spacing.
|
||||
ascent int // Height above the baseline.
|
||||
fontchars []fontchar // Character descriptions.
|
||||
img *plan9Image // Image holding the glyphs.
|
||||
}
|
||||
|
||||
func (f *subface) Close() error { return nil }
|
||||
func (f *subface) Kern(r0, r1 rune) fixed.Int26_6 { return 0 }
|
||||
|
||||
func (f *subface) Glyph(dot fixed.Point26_6, r rune) (
|
||||
dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
|
||||
|
||||
r -= f.firstRune
|
||||
if r < 0 || f.n <= int(r) {
|
||||
return image.Rectangle{}, nil, image.Point{}, 0, false
|
||||
}
|
||||
i := &f.fontchars[r+0]
|
||||
j := &f.fontchars[r+1]
|
||||
|
||||
minX := int(dot.X+32)>>6 + int(i.left)
|
||||
minY := int(dot.Y+32)>>6 + int(i.top) - f.ascent
|
||||
dr = image.Rectangle{
|
||||
Min: image.Point{
|
||||
X: minX,
|
||||
Y: minY,
|
||||
},
|
||||
Max: image.Point{
|
||||
X: minX + int(j.x-i.x),
|
||||
Y: minY + int(i.bottom) - int(i.top),
|
||||
},
|
||||
}
|
||||
return dr, f.img, image.Point{int(i.x), int(i.top)}, fixed.Int26_6(i.width) << 6, true
|
||||
}
|
||||
|
||||
func (f *subface) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
|
||||
r -= f.firstRune
|
||||
if r < 0 || f.n <= int(r) {
|
||||
return fixed.Rectangle26_6{}, 0, false
|
||||
}
|
||||
i := &f.fontchars[r+0]
|
||||
j := &f.fontchars[r+1]
|
||||
|
||||
bounds = fixed.R(
|
||||
int(i.left),
|
||||
int(i.top)-f.ascent,
|
||||
int(i.left)+int(j.x-i.x),
|
||||
int(i.bottom)-f.ascent,
|
||||
)
|
||||
return bounds, fixed.Int26_6(i.width) << 6, true
|
||||
}
|
||||
|
||||
func (f *subface) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
|
||||
r -= f.firstRune
|
||||
if r < 0 || f.n <= int(r) {
|
||||
return 0, false
|
||||
}
|
||||
return fixed.Int26_6(f.fontchars[r].width) << 6, true
|
||||
}
|
||||
|
||||
// runeRange maps a single rune range [lo, hi] to a lazily loaded subface. Both
|
||||
// ends of the range are inclusive.
|
||||
type runeRange struct {
|
||||
lo, hi rune
|
||||
offset rune // subfont index that the lo rune maps to.
|
||||
relFilename string
|
||||
subface *subface
|
||||
bad bool
|
||||
}
|
||||
|
||||
// face implements font.Face for a Plan 9 font.
|
||||
//
|
||||
// It maps multiple rune ranges to *subface values. Rune ranges may overlap;
|
||||
// the first match wins.
|
||||
type face struct {
|
||||
height int
|
||||
ascent int
|
||||
readFile func(relFilename string) ([]byte, error)
|
||||
runeRanges []runeRange
|
||||
}
|
||||
|
||||
func (f *face) Close() error { return nil }
|
||||
func (f *face) Kern(r0, r1 rune) fixed.Int26_6 { return 0 }
|
||||
|
||||
func (f *face) Glyph(dot fixed.Point26_6, r rune) (
|
||||
dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
|
||||
|
||||
if s, rr := f.subface(r); s != nil {
|
||||
return s.Glyph(dot, rr)
|
||||
}
|
||||
return image.Rectangle{}, nil, image.Point{}, 0, false
|
||||
}
|
||||
|
||||
func (f *face) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
|
||||
if s, rr := f.subface(r); s != nil {
|
||||
return s.GlyphBounds(rr)
|
||||
}
|
||||
return fixed.Rectangle26_6{}, 0, false
|
||||
}
|
||||
|
||||
func (f *face) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
|
||||
if s, rr := f.subface(r); s != nil {
|
||||
return s.GlyphAdvance(rr)
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (f *face) subface(r rune) (*subface, rune) {
|
||||
// Fall back on U+FFFD if we can't find r.
|
||||
for _, rr := range [2]rune{r, '\ufffd'} {
|
||||
// We have to do linear, not binary search. plan9port's
|
||||
// lucsans/unicode.8.font says:
|
||||
// 0x2591 0x2593 ../luc/Altshades.7.0
|
||||
// 0x2500 0x25ee ../luc/FormBlock.7.0
|
||||
// and the rune ranges overlap.
|
||||
for i := range f.runeRanges {
|
||||
x := &f.runeRanges[i]
|
||||
if rr < x.lo || x.hi < rr || x.bad {
|
||||
continue
|
||||
}
|
||||
if x.subface == nil {
|
||||
data, err := f.readFile(x.relFilename)
|
||||
if err != nil {
|
||||
log.Printf("plan9font: couldn't read subfont %q: %v", x.relFilename, err)
|
||||
x.bad = true
|
||||
continue
|
||||
}
|
||||
sub, err := ParseSubfont(data, x.lo-x.offset)
|
||||
if err != nil {
|
||||
log.Printf("plan9font: couldn't parse subfont %q: %v", x.relFilename, err)
|
||||
x.bad = true
|
||||
continue
|
||||
}
|
||||
x.subface = sub.(*subface)
|
||||
}
|
||||
return x.subface, rr
|
||||
}
|
||||
}
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// ParseFont parses a Plan 9 font file. data is the contents of that font file,
|
||||
// which gives relative filenames for subfont files. readFile returns the
|
||||
// contents of those subfont files. It is similar to io/ioutil's ReadFile
|
||||
// function, except that it takes a relative filename instead of an absolute
|
||||
// one.
|
||||
func ParseFont(data []byte, readFile func(relFilename string) ([]byte, error)) (font.Face, error) {
|
||||
f := &face{
|
||||
readFile: readFile,
|
||||
}
|
||||
// TODO: don't use strconv, to avoid the conversions from []byte to string?
|
||||
for first := true; len(data) > 0; first = false {
|
||||
i := bytes.IndexByte(data, '\n')
|
||||
if i < 0 {
|
||||
return nil, errors.New("plan9font: invalid font: no final newline")
|
||||
}
|
||||
row := string(data[:i])
|
||||
data = data[i+1:]
|
||||
if first {
|
||||
height, s, ok := nextInt32(row)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("plan9font: invalid font: invalid header %q", row)
|
||||
}
|
||||
ascent, s, ok := nextInt32(s)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("plan9font: invalid font: invalid header %q", row)
|
||||
}
|
||||
if height < 0 || 0xffff < height || ascent < 0 || 0xffff < ascent {
|
||||
return nil, fmt.Errorf("plan9font: invalid font: invalid header %q", row)
|
||||
}
|
||||
f.height, f.ascent = int(height), int(ascent)
|
||||
continue
|
||||
}
|
||||
lo, s, ok := nextInt32(row)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("plan9font: invalid font: invalid row %q", row)
|
||||
}
|
||||
hi, s, ok := nextInt32(s)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("plan9font: invalid font: invalid row %q", row)
|
||||
}
|
||||
offset, s, _ := nextInt32(s)
|
||||
|
||||
f.runeRanges = append(f.runeRanges, runeRange{
|
||||
lo: lo,
|
||||
hi: hi,
|
||||
offset: offset,
|
||||
relFilename: s,
|
||||
})
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func nextInt32(s string) (ret int32, remaining string, ok bool) {
|
||||
i := 0
|
||||
for ; i < len(s) && s[i] <= ' '; i++ {
|
||||
}
|
||||
j := i
|
||||
for ; j < len(s) && s[j] > ' '; j++ {
|
||||
}
|
||||
n, err := strconv.ParseInt(s[i:j], 0, 32)
|
||||
if err != nil {
|
||||
return 0, s, false
|
||||
}
|
||||
for ; j < len(s) && s[j] <= ' '; j++ {
|
||||
}
|
||||
return int32(n), s[j:], true
|
||||
}
|
||||
|
||||
// ParseSubfont parses a Plan 9 subfont file.
|
||||
//
|
||||
// firstRune is the first rune in the subfont file. For example, the
|
||||
// Phonetic.6.0 subfont, containing glyphs in the range U+0250 to U+02E9, would
|
||||
// set firstRune to '\u0250'.
|
||||
func ParseSubfont(data []byte, firstRune rune) (font.Face, error) {
|
||||
data, m, err := parseImage(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(data) < 3*12 {
|
||||
return nil, errors.New("plan9font: invalid subfont: header too short")
|
||||
}
|
||||
n := atoi(data[0*12:])
|
||||
height := atoi(data[1*12:])
|
||||
ascent := atoi(data[2*12:])
|
||||
data = data[3*12:]
|
||||
if len(data) != 6*(n+1) {
|
||||
return nil, errors.New("plan9font: invalid subfont: data length mismatch")
|
||||
}
|
||||
return &subface{
|
||||
firstRune: firstRune,
|
||||
n: n,
|
||||
height: height,
|
||||
ascent: ascent,
|
||||
fontchars: parseFontchars(data),
|
||||
img: m,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// plan9Image implements that subset of the Plan 9 image feature set that is
|
||||
// used by this font file format.
|
||||
//
|
||||
// Some features, such as the repl bit and a clip rectangle, are omitted for
|
||||
// simplicity.
|
||||
type plan9Image struct {
|
||||
depth int // Depth of the pixels in bits.
|
||||
width int // Width in bytes of a single scan line.
|
||||
rect image.Rectangle // Extent of the image.
|
||||
pix []byte // Pixel bits.
|
||||
}
|
||||
|
||||
func (m *plan9Image) byteoffset(x, y int) int {
|
||||
a := y * m.width
|
||||
if m.depth < 8 {
|
||||
// We need to always round down, but Go rounds toward zero.
|
||||
np := 8 / m.depth
|
||||
if x < 0 {
|
||||
return a + (x-np+1)/np
|
||||
}
|
||||
return a + x/np
|
||||
}
|
||||
return a + x*(m.depth/8)
|
||||
}
|
||||
|
||||
func (m *plan9Image) Bounds() image.Rectangle { return m.rect }
|
||||
func (m *plan9Image) ColorModel() color.Model { return color.AlphaModel }
|
||||
|
||||
func (m *plan9Image) At(x, y int) color.Color {
|
||||
if (image.Point{x, y}).In(m.rect) {
|
||||
b := m.pix[m.byteoffset(x, y)]
|
||||
switch m.depth {
|
||||
case 1:
|
||||
// CGrey, 1.
|
||||
mask := uint8(1 << uint8(7-x&7))
|
||||
if (b & mask) != 0 {
|
||||
return color.Alpha{0xff}
|
||||
}
|
||||
return color.Alpha{0x00}
|
||||
case 2:
|
||||
// CGrey, 2.
|
||||
shift := uint(x&3) << 1
|
||||
// Place pixel at top of word.
|
||||
y := b << shift
|
||||
y &= 0xc0
|
||||
// Replicate throughout.
|
||||
y |= y >> 2
|
||||
y |= y >> 4
|
||||
return color.Alpha{y}
|
||||
}
|
||||
}
|
||||
return color.Alpha{0x00}
|
||||
}
|
||||
|
||||
var compressed = []byte("compressed\n")
|
||||
|
||||
func parseImage(data []byte) (remainingData []byte, m *plan9Image, retErr error) {
|
||||
if !bytes.HasPrefix(data, compressed) {
|
||||
return nil, nil, errors.New("plan9font: unsupported uncompressed format")
|
||||
}
|
||||
data = data[len(compressed):]
|
||||
|
||||
const hdrSize = 5 * 12
|
||||
if len(data) < hdrSize {
|
||||
return nil, nil, errors.New("plan9font: invalid image: header too short")
|
||||
}
|
||||
hdr, data := data[:hdrSize], data[hdrSize:]
|
||||
|
||||
// Distinguish new channel descriptor from old ldepth. Channel descriptors
|
||||
// have letters as well as numbers, while ldepths are a single digit
|
||||
// formatted as %-11d.
|
||||
new := false
|
||||
for m := 0; m < 10; m++ {
|
||||
if hdr[m] != ' ' {
|
||||
new = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hdr[11] != ' ' {
|
||||
return nil, nil, errors.New("plan9font: invalid image: bad header")
|
||||
}
|
||||
if !new {
|
||||
return nil, nil, errors.New("plan9font: unsupported ldepth format")
|
||||
}
|
||||
|
||||
depth := 0
|
||||
switch s := strings.TrimSpace(string(hdr[:1*12])); s {
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("plan9font: unsupported pixel format %q", s)
|
||||
case "k1":
|
||||
depth = 1
|
||||
case "k2":
|
||||
depth = 2
|
||||
}
|
||||
r := ator(hdr[1*12:])
|
||||
if r.Min.X > r.Max.X || r.Min.Y > r.Max.Y {
|
||||
return nil, nil, errors.New("plan9font: invalid image: bad rectangle")
|
||||
}
|
||||
|
||||
width := bytesPerLine(r, depth)
|
||||
m = &plan9Image{
|
||||
depth: depth,
|
||||
width: width,
|
||||
rect: r,
|
||||
pix: make([]byte, width*r.Dy()),
|
||||
}
|
||||
|
||||
miny := r.Min.Y
|
||||
for miny != r.Max.Y {
|
||||
if len(data) < 2*12 {
|
||||
return nil, nil, errors.New("plan9font: invalid image: data band too short")
|
||||
}
|
||||
maxy := atoi(data[0*12:])
|
||||
nb := atoi(data[1*12:])
|
||||
data = data[2*12:]
|
||||
|
||||
if len(data) < nb {
|
||||
return nil, nil, errors.New("plan9font: invalid image: data band length mismatch")
|
||||
}
|
||||
buf := data[:nb]
|
||||
data = data[nb:]
|
||||
|
||||
if maxy <= miny || r.Max.Y < maxy {
|
||||
return nil, nil, fmt.Errorf("plan9font: bad maxy %d", maxy)
|
||||
}
|
||||
// An old-format image would flip the bits here, but we don't support
|
||||
// the old format.
|
||||
rr := r
|
||||
rr.Min.Y = miny
|
||||
rr.Max.Y = maxy
|
||||
if err := decompress(m, rr, buf); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
miny = maxy
|
||||
}
|
||||
return data, m, nil
|
||||
}
|
||||
|
||||
// Compressed data are sequences of byte codes. If the first byte b has the
|
||||
// 0x80 bit set, the next (b^0x80)+1 bytes are data. Otherwise, these two bytes
|
||||
// specify a previous string to repeat.
|
||||
const (
|
||||
compShortestMatch = 3 // shortest match possible.
|
||||
compWindowSize = 1024 // window size.
|
||||
)
|
||||
|
||||
var (
|
||||
errDecompressBufferTooSmall = errors.New("plan9font: decompress: buffer too small")
|
||||
errDecompressPhaseError = errors.New("plan9font: decompress: phase error")
|
||||
)
|
||||
|
||||
func decompress(m *plan9Image, r image.Rectangle, data []byte) error {
|
||||
if !r.In(m.rect) {
|
||||
return errors.New("plan9font: decompress: bad rectangle")
|
||||
}
|
||||
bpl := bytesPerLine(r, m.depth)
|
||||
mem := make([]byte, compWindowSize)
|
||||
memi := 0
|
||||
omemi := -1
|
||||
y := r.Min.Y
|
||||
linei := m.byteoffset(r.Min.X, y)
|
||||
eline := linei + bpl
|
||||
datai := 0
|
||||
for {
|
||||
if linei == eline {
|
||||
y++
|
||||
if y == r.Max.Y {
|
||||
break
|
||||
}
|
||||
linei = m.byteoffset(r.Min.X, y)
|
||||
eline = linei + bpl
|
||||
}
|
||||
if datai == len(data) {
|
||||
return errDecompressBufferTooSmall
|
||||
}
|
||||
c := data[datai]
|
||||
datai++
|
||||
if c >= 128 {
|
||||
for cnt := c - 128 + 1; cnt != 0; cnt-- {
|
||||
if datai == len(data) {
|
||||
return errDecompressBufferTooSmall
|
||||
}
|
||||
if linei == eline {
|
||||
return errDecompressPhaseError
|
||||
}
|
||||
m.pix[linei] = data[datai]
|
||||
linei++
|
||||
mem[memi] = data[datai]
|
||||
memi++
|
||||
datai++
|
||||
if memi == len(mem) {
|
||||
memi = 0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if datai == len(data) {
|
||||
return errDecompressBufferTooSmall
|
||||
}
|
||||
offs := int(data[datai]) + ((int(c) & 3) << 8) + 1
|
||||
datai++
|
||||
if memi < offs {
|
||||
omemi = memi + (compWindowSize - offs)
|
||||
} else {
|
||||
omemi = memi - offs
|
||||
}
|
||||
for cnt := (c >> 2) + compShortestMatch; cnt != 0; cnt-- {
|
||||
if linei == eline {
|
||||
return errDecompressPhaseError
|
||||
}
|
||||
m.pix[linei] = mem[omemi]
|
||||
linei++
|
||||
mem[memi] = mem[omemi]
|
||||
memi++
|
||||
omemi++
|
||||
if omemi == len(mem) {
|
||||
omemi = 0
|
||||
}
|
||||
if memi == len(mem) {
|
||||
memi = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ator(b []byte) image.Rectangle {
|
||||
return image.Rectangle{atop(b), atop(b[2*12:])}
|
||||
}
|
||||
|
||||
func atop(b []byte) image.Point {
|
||||
return image.Pt(atoi(b), atoi(b[12:]))
|
||||
}
|
||||
|
||||
func atoi(b []byte) int {
|
||||
i := 0
|
||||
for ; i < len(b) && b[i] == ' '; i++ {
|
||||
}
|
||||
n := 0
|
||||
for ; i < len(b) && '0' <= b[i] && b[i] <= '9'; i++ {
|
||||
n = n*10 + int(b[i]) - '0'
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func bytesPerLine(r image.Rectangle, depth int) int {
|
||||
if depth <= 0 || 32 < depth {
|
||||
panic("invalid depth")
|
||||
}
|
||||
var l int
|
||||
if r.Min.X >= 0 {
|
||||
l = (r.Max.X*depth + 7) / 8
|
||||
l -= (r.Min.X * depth) / 8
|
||||
} else {
|
||||
// Make positive before divide.
|
||||
t := (-r.Min.X*depth + 7) / 8
|
||||
l = t + (r.Max.X*depth+7)/8
|
||||
}
|
||||
return l
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
These font files were copied from the Plan 9 Port's font/fixed directory. The
|
||||
README in that directory states that: "These fonts are converted from the BDFs
|
||||
in the XFree86 distribution. They were all marked as public domain."
|
||||
|
||||
The Plan 9 Port is at https://github.com/9fans/plan9port and the copy was made
|
||||
from commit a78b1841 (2015-08-18).
|
||||
|
||||
The unicode.7x13.font file also refers to a ../shinonome directory, but this
|
||||
testdata does not include those subfont files.
|
|
@ -0,0 +1,68 @@
|
|||
13 10
|
||||
0x0000 0x001F 7x13.2400
|
||||
0x0000 0x00FF 7x13.0000
|
||||
0x0100 0x01FF 7x13.0100
|
||||
0x0200 0x02FF 7x13.0200
|
||||
0x0300 0x03FF 7x13.0300
|
||||
0x0400 0x04FF 7x13.0400
|
||||
0x0500 0x05FF 7x13.0500
|
||||
0x0E00 0x0EFF 7x13.0E00
|
||||
0x1000 0x10FF 7x13.1000
|
||||
0x1600 0x16FF 7x13.1600
|
||||
0x1E00 0x1EFF 7x13.1E00
|
||||
0x1F00 0x1FFF 7x13.1F00
|
||||
0x2000 0x20FF 7x13.2000
|
||||
0x2100 0x21FF 7x13.2100
|
||||
0x2200 0x22FF 7x13.2200
|
||||
0x2300 0x23FF 7x13.2300
|
||||
0x2400 0x24FF 7x13.2400
|
||||
0x2500 0x25FF 7x13.2500
|
||||
0x2600 0x26FF 7x13.2600
|
||||
0x2700 0x27FF 7x13.2700
|
||||
0x2800 0x28FF 7x13.2800
|
||||
0x2A00 0x2AFF 7x13.2A00
|
||||
0x3000 0x30fe ../shinonome/k12.3000
|
||||
0x4e00 0x4ffe ../shinonome/k12.4e00
|
||||
0x5005 0x51fe ../shinonome/k12.5005
|
||||
0x5200 0x53fa ../shinonome/k12.5200
|
||||
0x5401 0x55fe ../shinonome/k12.5401
|
||||
0x5606 0x57fc ../shinonome/k12.5606
|
||||
0x5800 0x59ff ../shinonome/k12.5800
|
||||
0x5a01 0x5bff ../shinonome/k12.5a01
|
||||
0x5c01 0x5dfe ../shinonome/k12.5c01
|
||||
0x5e02 0x5fff ../shinonome/k12.5e02
|
||||
0x600e 0x61ff ../shinonome/k12.600e
|
||||
0x6200 0x63fa ../shinonome/k12.6200
|
||||
0x6406 0x65fb ../shinonome/k12.6406
|
||||
0x6602 0x67ff ../shinonome/k12.6602
|
||||
0x6802 0x69ff ../shinonome/k12.6802
|
||||
0x6a02 0x6bf3 ../shinonome/k12.6a02
|
||||
0x6c08 0x6dfb ../shinonome/k12.6c08
|
||||
0x6e05 0x6ffe ../shinonome/k12.6e05
|
||||
0x7001 0x71ff ../shinonome/k12.7001
|
||||
0x7206 0x73fe ../shinonome/k12.7206
|
||||
0x7403 0x75ff ../shinonome/k12.7403
|
||||
0x7601 0x77fc ../shinonome/k12.7601
|
||||
0x7802 0x79fb ../shinonome/k12.7802
|
||||
0x7a00 0x7bf7 ../shinonome/k12.7a00
|
||||
0x7c00 0x7dfb ../shinonome/k12.7c00
|
||||
0x7e01 0x7ffc ../shinonome/k12.7e01
|
||||
0x8000 0x81fe ../shinonome/k12.8000
|
||||
0x8201 0x83fd ../shinonome/k12.8201
|
||||
0x8403 0x85fe ../shinonome/k12.8403
|
||||
0x8602 0x87fe ../shinonome/k12.8602
|
||||
0x8805 0x89f8 ../shinonome/k12.8805
|
||||
0x8a00 0x8b9a ../shinonome/k12.8a00
|
||||
0x8c37 0x8dff ../shinonome/k12.8c37
|
||||
0x8e08 0x8ffd ../shinonome/k12.8e08
|
||||
0x9000 0x91ff ../shinonome/k12.9000
|
||||
0x920d 0x93e8 ../shinonome/k12.920d
|
||||
0x9403 0x95e5 ../shinonome/k12.9403
|
||||
0x961c 0x97ff ../shinonome/k12.961c
|
||||
0x9801 0x99ff ../shinonome/k12.9801
|
||||
0x9a01 0x9bf5 ../shinonome/k12.9a01
|
||||
0x9c04 0x9dfd ../shinonome/k12.9c04
|
||||
0x9e1a 0x9fa0 ../shinonome/k12.9e1a
|
||||
0xFB00 0xFBFF 7x13.FB00
|
||||
0xFE00 0xFEFF 7x13.FE00
|
||||
0xFF00 0xFFFF 7x13.FF00
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2015 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 f32 implements float32 vector and matrix types.
|
||||
package f32 // import "golang.org/x/image/math/f32"
|
||||
|
||||
// Vec2 is a 2-element vector.
|
||||
type Vec2 [2]float32
|
||||
|
||||
// Vec3 is a 3-element vector.
|
||||
type Vec3 [3]float32
|
||||
|
||||
// Vec4 is a 4-element vector.
|
||||
type Vec4 [4]float32
|
||||
|
||||
// Mat3 is a 3x3 matrix in row major order.
|
||||
//
|
||||
// m[3*r + c] is the element in the r'th row and c'th column.
|
||||
type Mat3 [9]float32
|
||||
|
||||
// Mat4 is a 4x4 matrix in row major order.
|
||||
//
|
||||
// m[4*r + c] is the element in the r'th row and c'th column.
|
||||
type Mat4 [16]float32
|
||||
|
||||
// Aff3 is a 3x3 affine transformation matrix in row major order, where the
|
||||
// bottom row is implicitly [0 0 1].
|
||||
//
|
||||
// m[3*r + c] is the element in the r'th row and c'th column.
|
||||
type Aff3 [6]float32
|
||||
|
||||
// Aff4 is a 4x4 affine transformation matrix in row major order, where the
|
||||
// bottom row is implicitly [0 0 0 1].
|
||||
//
|
||||
// m[4*r + c] is the element in the r'th row and c'th column.
|
||||
type Aff4 [12]float32
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2015 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 f64 implements float64 vector and matrix types.
|
||||
package f64 // import "golang.org/x/image/math/f64"
|
||||
|
||||
// Vec2 is a 2-element vector.
|
||||
type Vec2 [2]float64
|
||||
|
||||
// Vec3 is a 3-element vector.
|
||||
type Vec3 [3]float64
|
||||
|
||||
// Vec4 is a 4-element vector.
|
||||
type Vec4 [4]float64
|
||||
|
||||
// Mat3 is a 3x3 matrix in row major order.
|
||||
//
|
||||
// m[3*r + c] is the element in the r'th row and c'th column.
|
||||
type Mat3 [9]float64
|
||||
|
||||
// Mat4 is a 4x4 matrix in row major order.
|
||||
//
|
||||
// m[4*r + c] is the element in the r'th row and c'th column.
|
||||
type Mat4 [16]float64
|
||||
|
||||
// Aff3 is a 3x3 affine transformation matrix in row major order, where the
|
||||
// bottom row is implicitly [0 0 1].
|
||||
//
|
||||
// m[3*r + c] is the element in the r'th row and c'th column.
|
||||
type Aff3 [6]float64
|
||||
|
||||
// Aff4 is a 4x4 affine transformation matrix in row major order, where the
|
||||
// bottom row is implicitly [0 0 0 1].
|
||||
//
|
||||
// m[4*r + c] is the element in the r'th row and c'th column.
|
||||
type Aff4 [12]float64
|
|
@ -0,0 +1,172 @@
|
|||
// Copyright 2015 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 fixed implements fixed-point integer types.
|
||||
package fixed // import "golang.org/x/image/math/fixed"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// TODO: implement fmt.Formatter for %f and %g.
|
||||
|
||||
// I returns the integer value i as an Int26_6.
|
||||
//
|
||||
// For example, passing the integer value 2 yields Int26_6(128).
|
||||
func I(i int) Int26_6 {
|
||||
return Int26_6(i << 6)
|
||||
}
|
||||
|
||||
// Int26_6 is a signed 26.6 fixed-point number.
|
||||
//
|
||||
// The integer part ranges from -33554432 to 33554431, inclusive. The
|
||||
// fractional part has 6 bits of precision.
|
||||
//
|
||||
// For example, the number one-and-a-quarter is Int26_6(1<<6 + 1<<4).
|
||||
type Int26_6 int32
|
||||
|
||||
// String returns a human-readable representation of a 26.6 fixed-point number.
|
||||
//
|
||||
// For example, the number one-and-a-quarter becomes "1:16".
|
||||
func (x Int26_6) String() string {
|
||||
const shift, mask = 6, 1<<6 - 1
|
||||
if x >= 0 {
|
||||
return fmt.Sprintf("%d:%02d", int32(x>>shift), int32(x&mask))
|
||||
}
|
||||
x = -x
|
||||
if x >= 0 {
|
||||
return fmt.Sprintf("-%d:%02d", int32(x>>shift), int32(x&mask))
|
||||
}
|
||||
return "-33554432:00" // The minimum value is -(1<<25).
|
||||
}
|
||||
|
||||
// Int52_12 is a signed 52.12 fixed-point number.
|
||||
//
|
||||
// The integer part ranges from -2251799813685248 to 2251799813685247,
|
||||
// inclusive. The fractional part has 12 bits of precision.
|
||||
//
|
||||
// For example, the number one-and-a-quarter is Int52_12(1<<12 + 1<<10).
|
||||
type Int52_12 int64
|
||||
|
||||
// String returns a human-readable representation of a 52.12 fixed-point
|
||||
// number.
|
||||
//
|
||||
// For example, the number one-and-a-quarter becomes "1:1024".
|
||||
func (x Int52_12) String() string {
|
||||
const shift, mask = 12, 1<<12 - 1
|
||||
if x >= 0 {
|
||||
return fmt.Sprintf("%d:%04d", int64(x>>shift), int64(x&mask))
|
||||
}
|
||||
x = -x
|
||||
if x >= 0 {
|
||||
return fmt.Sprintf("-%d:%04d", int64(x>>shift), int64(x&mask))
|
||||
}
|
||||
return "-2251799813685248:0000" // The minimum value is -(1<<51).
|
||||
}
|
||||
|
||||
// P returns the integer values x and y as a Point26_6.
|
||||
//
|
||||
// For example, passing the integer values (2, -3) yields Point26_6{128, -192}.
|
||||
func P(x, y int) Point26_6 {
|
||||
return Point26_6{Int26_6(x << 6), Int26_6(y << 6)}
|
||||
}
|
||||
|
||||
// Point26_6 is a 26.6 fixed-point coordinate pair.
|
||||
//
|
||||
// It is analogous to the image.Point type in the standard library.
|
||||
type Point26_6 struct {
|
||||
X, Y Int26_6
|
||||
}
|
||||
|
||||
// Add returns the vector p+q.
|
||||
func (p Point26_6) Add(q Point26_6) Point26_6 {
|
||||
return Point26_6{p.X + q.X, p.Y + q.Y}
|
||||
}
|
||||
|
||||
// Sub returns the vector p-q.
|
||||
func (p Point26_6) Sub(q Point26_6) Point26_6 {
|
||||
return Point26_6{p.X - q.X, p.Y - q.Y}
|
||||
}
|
||||
|
||||
// Mul returns the vector p*k.
|
||||
func (p Point26_6) Mul(k Int26_6) Point26_6 {
|
||||
return Point26_6{p.X * k / 64, p.Y * k / 64}
|
||||
}
|
||||
|
||||
// Div returns the vector p/k.
|
||||
func (p Point26_6) Div(k Int26_6) Point26_6 {
|
||||
return Point26_6{p.X * 64 / k, p.Y * 64 / k}
|
||||
}
|
||||
|
||||
// Point52_12 is a 52.12 fixed-point coordinate pair.
|
||||
//
|
||||
// It is analogous to the image.Point type in the standard library.
|
||||
type Point52_12 struct {
|
||||
X, Y Int52_12
|
||||
}
|
||||
|
||||
// Add returns the vector p+q.
|
||||
func (p Point52_12) Add(q Point52_12) Point52_12 {
|
||||
return Point52_12{p.X + q.X, p.Y + q.Y}
|
||||
}
|
||||
|
||||
// Sub returns the vector p-q.
|
||||
func (p Point52_12) Sub(q Point52_12) Point52_12 {
|
||||
return Point52_12{p.X - q.X, p.Y - q.Y}
|
||||
}
|
||||
|
||||
// Mul returns the vector p*k.
|
||||
func (p Point52_12) Mul(k Int52_12) Point52_12 {
|
||||
return Point52_12{p.X * k / 4096, p.Y * k / 4096}
|
||||
}
|
||||
|
||||
// Div returns the vector p/k.
|
||||
func (p Point52_12) Div(k Int52_12) Point52_12 {
|
||||
return Point52_12{p.X * 4096 / k, p.Y * 4096 / k}
|
||||
}
|
||||
|
||||
// R returns the integer values minX, minY, maxX, maxY as a Rectangle26_6.
|
||||
//
|
||||
// For example, passing the integer values (0, 1, 2, 3) yields
|
||||
// Rectangle26_6{Point26_6{0, 64}, Point26_6{128, 192}}.
|
||||
//
|
||||
// Like the image.Rect function in the standard library, the returned rectangle
|
||||
// has minimum and maximum coordinates swapped if necessary so that it is
|
||||
// well-formed.
|
||||
func R(minX, minY, maxX, maxY int) Rectangle26_6 {
|
||||
if minX > maxX {
|
||||
minX, maxX = maxX, minX
|
||||
}
|
||||
if minY > maxY {
|
||||
minY, maxY = maxY, minY
|
||||
}
|
||||
return Rectangle26_6{
|
||||
Point26_6{
|
||||
Int26_6(minX << 6),
|
||||
Int26_6(minY << 6),
|
||||
},
|
||||
Point26_6{
|
||||
Int26_6(maxX << 6),
|
||||
Int26_6(maxY << 6),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Rectangle26_6 is a 26.6 fixed-point coordinate rectangle. The Min bound is
|
||||
// inclusive and the Max bound is exclusive. It is well-formed if Min.X <=
|
||||
// Max.X and likewise for Y.
|
||||
//
|
||||
// It is analogous to the image.Rectangle type in the standard library.
|
||||
type Rectangle26_6 struct {
|
||||
Min, Max Point26_6
|
||||
}
|
||||
|
||||
// Rectangle52_12 is a 52.12 fixed-point coordinate rectangle. The Min bound is
|
||||
// inclusive and the Max bound is exclusive. It is well-formed if Min.X <=
|
||||
// Max.X and likewise for Y.
|
||||
//
|
||||
// It is analogous to the image.Rectangle type in the standard library.
|
||||
type Rectangle52_12 struct {
|
||||
Min, Max Point52_12
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2015 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 fixed
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInt26_6(t *testing.T) {
|
||||
got := Int26_6(1<<6 + 1<<4).String()
|
||||
want := "1:16"
|
||||
if got != want {
|
||||
t.Fatalf("got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt52_12(t *testing.T) {
|
||||
got := Int52_12(1<<12 + 1<<10).String()
|
||||
want := "1:1024"
|
||||
if got != want {
|
||||
t.Fatalf("got %q, want %q", got, want)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
// Copyright 2014 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 riff_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/image/riff"
|
||||
)
|
||||
|
||||
func ExampleReader() {
|
||||
formType, r, err := riff.NewReader(strings.NewReader(data))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("RIFF(%s)\n", formType)
|
||||
if err := dump(r, ".\t"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Output:
|
||||
// RIFF(ROOT)
|
||||
// . ZERO ""
|
||||
// . ONE "a"
|
||||
// . LIST(META)
|
||||
// . . LIST(GOOD)
|
||||
// . . . ONE "a"
|
||||
// . . . FIVE "klmno"
|
||||
// . . ZERO ""
|
||||
// . . LIST(BAD )
|
||||
// . . . THRE "def"
|
||||
// . TWO "bc"
|
||||
// . LIST(UGLY)
|
||||
// . . FOUR "ghij"
|
||||
// . . SIX "pqrstu"
|
||||
}
|
||||
|
||||
func dump(r *riff.Reader, indent string) error {
|
||||
for {
|
||||
chunkID, chunkLen, chunkData, err := r.Next()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if chunkID == riff.LIST {
|
||||
listType, list, err := riff.NewListReader(chunkLen, chunkData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%sLIST(%s)\n", indent, listType)
|
||||
if err := dump(list, indent+".\t"); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
b, err := ioutil.ReadAll(chunkData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%s%s %q\n", indent, chunkID, b)
|
||||
}
|
||||
}
|
||||
|
||||
func encodeU32(u uint32) string {
|
||||
return string([]byte{
|
||||
byte(u >> 0),
|
||||
byte(u >> 8),
|
||||
byte(u >> 16),
|
||||
byte(u >> 24),
|
||||
})
|
||||
}
|
||||
|
||||
func encode(chunkID, contents string) string {
|
||||
n := len(contents)
|
||||
if n&1 == 1 {
|
||||
contents += "\x00"
|
||||
}
|
||||
return chunkID + encodeU32(uint32(n)) + contents
|
||||
}
|
||||
|
||||
func encodeMulti(typ0, typ1 string, chunks ...string) string {
|
||||
n := 4
|
||||
for _, c := range chunks {
|
||||
n += len(c)
|
||||
}
|
||||
s := typ0 + encodeU32(uint32(n)) + typ1
|
||||
for _, c := range chunks {
|
||||
s += c
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
var (
|
||||
d0 = encode("ZERO", "")
|
||||
d1 = encode("ONE ", "a")
|
||||
d2 = encode("TWO ", "bc")
|
||||
d3 = encode("THRE", "def")
|
||||
d4 = encode("FOUR", "ghij")
|
||||
d5 = encode("FIVE", "klmno")
|
||||
d6 = encode("SIX ", "pqrstu")
|
||||
l0 = encodeMulti("LIST", "GOOD", d1, d5)
|
||||
l1 = encodeMulti("LIST", "BAD ", d3)
|
||||
l2 = encodeMulti("LIST", "UGLY", d4, d6)
|
||||
l01 = encodeMulti("LIST", "META", l0, d0, l1)
|
||||
data = encodeMulti("RIFF", "ROOT", d0, d1, l01, d2, l2)
|
||||
)
|
|
@ -0,0 +1,179 @@
|
|||
// Copyright 2014 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 riff implements the Resource Interchange File Format, used by media
|
||||
// formats such as AVI, WAVE and WEBP.
|
||||
//
|
||||
// A RIFF stream contains a sequence of chunks. Each chunk consists of an 8-byte
|
||||
// header (containing a 4-byte chunk type and a 4-byte chunk length), the chunk
|
||||
// data (presented as an io.Reader), and some padding bytes.
|
||||
//
|
||||
// A detailed description of the format is at
|
||||
// http://www.tactilemedia.com/info/MCI_Control_Info.html
|
||||
package riff // import "golang.org/x/image/riff"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
)
|
||||
|
||||
var (
|
||||
errMissingPaddingByte = errors.New("riff: missing padding byte")
|
||||
errMissingRIFFChunkHeader = errors.New("riff: missing RIFF chunk header")
|
||||
errShortChunkData = errors.New("riff: short chunk data")
|
||||
errShortChunkHeader = errors.New("riff: short chunk header")
|
||||
errStaleReader = errors.New("riff: stale reader")
|
||||
)
|
||||
|
||||
// u32 decodes the first four bytes of b as a little-endian integer.
|
||||
func u32(b []byte) uint32 {
|
||||
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
||||
}
|
||||
|
||||
const chunkHeaderSize = 8
|
||||
|
||||
// FourCC is a four character code.
|
||||
type FourCC [4]byte
|
||||
|
||||
// LIST is the "LIST" FourCC.
|
||||
var LIST = FourCC{'L', 'I', 'S', 'T'}
|
||||
|
||||
// NewReader returns the RIFF stream's form type, such as "AVI " or "WAVE", and
|
||||
// its chunks as a *Reader.
|
||||
func NewReader(r io.Reader) (formType FourCC, data *Reader, err error) {
|
||||
var buf [chunkHeaderSize]byte
|
||||
if _, err := io.ReadFull(r, buf[:]); err != nil {
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
err = errMissingRIFFChunkHeader
|
||||
}
|
||||
return FourCC{}, nil, err
|
||||
}
|
||||
if buf[0] != 'R' || buf[1] != 'I' || buf[2] != 'F' || buf[3] != 'F' {
|
||||
return FourCC{}, nil, errMissingRIFFChunkHeader
|
||||
}
|
||||
return NewListReader(u32(buf[4:]), r)
|
||||
}
|
||||
|
||||
// NewListReader returns a LIST chunk's list type, such as "movi" or "wavl",
|
||||
// and its chunks as a *Reader.
|
||||
func NewListReader(chunkLen uint32, chunkData io.Reader) (listType FourCC, data *Reader, err error) {
|
||||
if chunkLen < 4 {
|
||||
return FourCC{}, nil, errShortChunkData
|
||||
}
|
||||
z := &Reader{r: chunkData}
|
||||
if _, err := io.ReadFull(chunkData, z.buf[:4]); err != nil {
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
err = errShortChunkData
|
||||
}
|
||||
return FourCC{}, nil, err
|
||||
}
|
||||
z.totalLen = chunkLen - 4
|
||||
return FourCC{z.buf[0], z.buf[1], z.buf[2], z.buf[3]}, z, nil
|
||||
}
|
||||
|
||||
// Reader reads chunks from an underlying io.Reader.
|
||||
type Reader struct {
|
||||
r io.Reader
|
||||
err error
|
||||
|
||||
totalLen uint32
|
||||
chunkLen uint32
|
||||
|
||||
chunkReader *chunkReader
|
||||
buf [chunkHeaderSize]byte
|
||||
padded bool
|
||||
}
|
||||
|
||||
// Next returns the next chunk's ID, length and data. It returns io.EOF if there
|
||||
// are no more chunks. The io.Reader returned becomes stale after the next Next
|
||||
// call, and should no longer be used.
|
||||
//
|
||||
// It is valid to call Next even if all of the previous chunk's data has not
|
||||
// been read.
|
||||
func (z *Reader) Next() (chunkID FourCC, chunkLen uint32, chunkData io.Reader, err error) {
|
||||
if z.err != nil {
|
||||
return FourCC{}, 0, nil, z.err
|
||||
}
|
||||
|
||||
// Drain the rest of the previous chunk.
|
||||
if z.chunkLen != 0 {
|
||||
_, z.err = io.Copy(ioutil.Discard, z.chunkReader)
|
||||
if z.err != nil {
|
||||
return FourCC{}, 0, nil, z.err
|
||||
}
|
||||
}
|
||||
z.chunkReader = nil
|
||||
if z.padded {
|
||||
_, z.err = io.ReadFull(z.r, z.buf[:1])
|
||||
if z.err != nil {
|
||||
if z.err == io.EOF {
|
||||
z.err = errMissingPaddingByte
|
||||
}
|
||||
return FourCC{}, 0, nil, z.err
|
||||
}
|
||||
z.totalLen--
|
||||
}
|
||||
|
||||
// We are done if we have no more data.
|
||||
if z.totalLen == 0 {
|
||||
z.err = io.EOF
|
||||
return FourCC{}, 0, nil, z.err
|
||||
}
|
||||
|
||||
// Read the next chunk header.
|
||||
if z.totalLen < chunkHeaderSize {
|
||||
z.err = errShortChunkHeader
|
||||
return FourCC{}, 0, nil, z.err
|
||||
}
|
||||
z.totalLen -= chunkHeaderSize
|
||||
if _, err = io.ReadFull(z.r, z.buf[:chunkHeaderSize]); err != nil {
|
||||
if z.err == io.EOF || z.err == io.ErrUnexpectedEOF {
|
||||
z.err = errShortChunkHeader
|
||||
}
|
||||
return FourCC{}, 0, nil, z.err
|
||||
}
|
||||
chunkID = FourCC{z.buf[0], z.buf[1], z.buf[2], z.buf[3]}
|
||||
z.chunkLen = u32(z.buf[4:])
|
||||
z.padded = z.chunkLen&1 == 1
|
||||
z.chunkReader = &chunkReader{z}
|
||||
return chunkID, z.chunkLen, z.chunkReader, nil
|
||||
}
|
||||
|
||||
type chunkReader struct {
|
||||
z *Reader
|
||||
}
|
||||
|
||||
func (c *chunkReader) Read(p []byte) (int, error) {
|
||||
if c != c.z.chunkReader {
|
||||
return 0, errStaleReader
|
||||
}
|
||||
z := c.z
|
||||
if z.err != nil {
|
||||
if z.err == io.EOF {
|
||||
return 0, errStaleReader
|
||||
}
|
||||
return 0, z.err
|
||||
}
|
||||
|
||||
n := int(z.chunkLen)
|
||||
if n == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if n < 0 {
|
||||
// Converting uint32 to int overflowed.
|
||||
n = math.MaxInt32
|
||||
}
|
||||
if n > len(p) {
|
||||
n = len(p)
|
||||
}
|
||||
n, err := z.r.Read(p[:n])
|
||||
z.totalLen -= uint32(n)
|
||||
z.chunkLen -= uint32(n)
|
||||
if err != io.EOF {
|
||||
z.err = err
|
||||
}
|
||||
return n, err
|
||||
}
|
After Width: | Height: | Size: 171 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 116 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 139 KiB |
After Width: | Height: | Size: 249 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 129 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 798 B |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 442 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 772 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.4 KiB |