shiny/font: new package for drawing text on an image.
Package font defines an interface for font faces. Other packages provide font face implementations. For example, a truetype package (not part of this CL) would provide one based on .ttf font files. This CL also introduces the golang.org/x/exp/shiny/font/plan9font package, a concrete implementation of the font.Face interface for the Plan 9 bitmap font format. Change-Id: Iead8914caaa58c7562b18a86b45002ae47486903 Reviewed-on: https://go-review.googlesource.com/13463 Reviewed-by: Rob Pike <r@golang.org>
This commit is contained in:
parent
8ca9b58be9
commit
eecb4e626f
84
example/font/main.go
Normal file
84
example/font/main.go
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
// 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/exp/shiny/..." 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"
|
||||||
|
|
||||||
|
"golang.org/x/exp/shiny/font"
|
||||||
|
"golang.org/x/exp/shiny/font/plan9font"
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
subfont = flag.String("subfont", "", `filename of the Plan 9 subfont file, such as "lucsans/lsr.14"`)
|
||||||
|
firstRune = 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 file.
|
||||||
|
if *subfont == "" {
|
||||||
|
flag.Usage()
|
||||||
|
log.Fatal("no subfont specified")
|
||||||
|
}
|
||||||
|
fontData, err := ioutil.ReadFile(*subfont)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
face, err := plan9font.ParseSubfont(fontData, rune(*firstRune))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := image.NewRGBA(image.Rect(0, 0, 800, 100))
|
||||||
|
draw.Draw(dst, dst.Bounds(), image.Black, image.Point{}, draw.Src)
|
||||||
|
|
||||||
|
d := &font.Drawer{
|
||||||
|
Dst: dst,
|
||||||
|
Src: image.White,
|
||||||
|
Face: face,
|
||||||
|
Dot: fixed.Point26_6{
|
||||||
|
X: 20 << 6,
|
||||||
|
Y: 80 << 6,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
dot0 := pt(d.Dot)
|
||||||
|
d.DrawString("The quick brown fox jumps over the lazy dog.")
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
114
font/font.go
Normal file
114
font/font.go
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// TODO: move this from golang.org/x/exp to golang.org/x/image ??
|
||||||
|
|
||||||
|
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. It also returns the new
|
||||||
|
// dot after adding the 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) (
|
||||||
|
newDot fixed.Point26_6, dr image.Rectangle, mask image.Image, maskp image.Point, 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 and per-glyph Metrics.
|
||||||
|
// TODO: ColoredGlyph for various emoji?
|
||||||
|
// TODO: Ligatures? Shaping?
|
||||||
|
}
|
||||||
|
|
||||||
|
type MultiFace struct {
|
||||||
|
// TODO.
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
newDot, dr, mask, maskp, 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?
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
draw.DrawMask(d.Dst, dr, d.Src, image.Point{}, mask, maskp, draw.Over)
|
||||||
|
d.Dot, prevC = newDot, c
|
||||||
|
}
|
||||||
|
}
|
388
font/plan9font/plan9font.go
Normal file
388
font/plan9font/plan9font.go
Normal file
|
@ -0,0 +1,388 @@
|
||||||
|
// 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 file format.
|
||||||
|
package plan9font
|
||||||
|
|
||||||
|
// TODO: have a face 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"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/exp/shiny/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
|
||||||
|
}
|
||||||
|
|
||||||
|
// face implements font.Face.
|
||||||
|
type face 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 *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) (
|
||||||
|
newDot fixed.Point26_6, dr image.Rectangle, mask image.Image, maskp image.Point, ok bool) {
|
||||||
|
|
||||||
|
r -= f.firstRune
|
||||||
|
if r < 0 || f.n <= int(r) {
|
||||||
|
return fixed.Point26_6{}, image.Rectangle{}, nil, image.Point{}, false
|
||||||
|
}
|
||||||
|
i := &f.fontchars[r+0]
|
||||||
|
j := &f.fontchars[r+1]
|
||||||
|
|
||||||
|
newDot = fixed.Point26_6{
|
||||||
|
X: dot.X + fixed.Int26_6(i.width)<<6,
|
||||||
|
Y: dot.Y,
|
||||||
|
}
|
||||||
|
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 newDot, dr, f.img, image.Point{int(i.x), int(i.top)}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFont parses a Plan 9 font file.
|
||||||
|
func ParseFont(data []byte, openFunc func(name string) (io.ReadCloser, error)) (*font.MultiFace, error) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 &face{
|
||||||
|
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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user