add gbk encoder and decoder

This commit is contained in:
mxi4oyu 2017-09-12 18:12:27 +08:00
parent bf43273def
commit 265a8089ec
586 changed files with 377551 additions and 0 deletions

10
x/image/.gitattributes vendored Normal file
View File

@ -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

2
x/image/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# Add no patterns to .hgignore except for files generated by the build.
last-change

3
x/image/AUTHORS Normal file
View File

@ -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.

31
x/image/CONTRIBUTING.md Normal file
View File

@ -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.

3
x/image/CONTRIBUTORS Normal file
View 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.

27
x/image/LICENSE Normal file
View File

@ -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.

22
x/image/PATENTS Normal file
View File

@ -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.

3
x/image/README Normal file
View File

@ -0,0 +1,3 @@
This repository holds supplementary Go image libraries.
To submit changes to this repository, see http://golang.org/doc/contribute.html.

199
x/image/bmp/reader.go Normal file
View File

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

View File

@ -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
}
}
}

166
x/image/bmp/writer.go Normal file
View File

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

View File

@ -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)
}
}

View File

@ -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()
}

1
x/image/codereview.cfg Normal file
View File

@ -0,0 +1 @@
issuerepo: golang/go

View File

@ -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

View File

@ -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},
}

187
x/image/colornames/gen.go Normal file
View File

@ -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)
}
}

307
x/image/colornames/table.go Normal file
View File

@ -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",
}

79
x/image/draw/draw.go Normal file
View File

@ -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
}

View File

@ -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).
}

1403
x/image/draw/gen.go Normal file

File diff suppressed because it is too large Load Diff

6668
x/image/draw/impl.go Normal file

File diff suppressed because it is too large Load Diff

527
x/image/draw/scale.go Normal file
View File

@ -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()
}

731
x/image/draw/scale_test.go Normal file
View File

@ -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) }

View File

@ -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)
}
}
}
}
}
}
}
}

View File

@ -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)
}
}

View File

@ -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
}

File diff suppressed because it is too large Load Diff

View File

@ -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"
`

202
x/image/font/font.go Normal file
View File

@ -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
)

View File

@ -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...........
}

View File

@ -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
}

BIN
x/image/font/testdata/fixed/7x13.0000 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.0100 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.0200 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.0300 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.0400 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.0500 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.0E00 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.1000 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.1600 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.1E00 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.1F00 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.2000 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.2100 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.2200 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.2300 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.2400 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.2500 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.2600 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.2700 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.2800 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.2A00 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.3000 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.FB00 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.FE00 vendored Normal file

Binary file not shown.

BIN
x/image/font/testdata/fixed/7x13.FF00 vendored Normal file

Binary file not shown.

9
x/image/font/testdata/fixed/README vendored Normal file
View File

@ -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.

View File

@ -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

37
x/image/math/f32/f32.go Normal file
View File

@ -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

37
x/image/math/f64/f64.go Normal file
View File

@ -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

172
x/image/math/fixed/fixed.go Normal file
View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
)

179
x/image/riff/riff.go Normal file
View File

@ -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
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

BIN
x/image/testdata/blue-purple-pink.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
x/image/testdata/bw-deflate.tiff vendored Normal file

Binary file not shown.

BIN
x/image/testdata/bw-packbits.tiff vendored Normal file

Binary file not shown.

BIN
x/image/testdata/bw-uncompressed.tiff vendored Normal file

Binary file not shown.

BIN
x/image/testdata/go-turns-two-14x18.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
x/image/testdata/go-turns-two-up-ab.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
x/image/testdata/go-turns-two-up-bl.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
x/image/testdata/go-turns-two-up-cr.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
x/image/testdata/go-turns-two-up-nn.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

BIN
x/image/testdata/gopher-doc.1bpp.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

BIN
x/image/testdata/gopher-doc.2bpp.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Some files were not shown because too many files have changed in this diff Show More