├── .travis.yml ├── LICENSE ├── README.md ├── decode_test.go ├── encode_test.go ├── examples ├── ff2png │ └── main.go └── img2ff │ └── main.go └── farbfeld.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.4 5 | - 1.5 6 | - 1.6 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC-License 2 | 3 | (c) 2014-2015, Robert Hülle 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Go.Farbfeld 2 | ============= 3 | 4 | [![Build Status](https://travis-ci.org/hullerob/go.farbfeld.svg?branch=master)](https://travis-ci.org/hullerob/go.farbfeld) 5 | 6 | About 7 | ----- 8 | 9 | This is Go implementation of [`farbfeld` image format](http://git.suckless.org/farbfeld/). 10 | 11 | It uses Go's `image` interface, similar to `image/png`. 12 | 13 | 14 | Install 15 | ------- 16 | 17 | go get github.com/hullerob/go.farbfeld 18 | 19 | Usage 20 | ----- 21 | 22 | See `examples`. 23 | -------------------------------------------------------------------------------- /decode_test.go: -------------------------------------------------------------------------------- 1 | package imagefile 2 | 3 | import ( 4 | "bytes" 5 | "image" 6 | "io" 7 | "testing" 8 | ) 9 | 10 | var decodeTests = []struct { 11 | descr string 12 | input []byte 13 | img []byte 14 | w, h int 15 | }{ 16 | { 17 | "empty image", 18 | []byte("farbfeld\000\000\000\000\000\000\000\000"), 19 | []byte{}, 20 | 0, 0, 21 | }, 22 | { 23 | "image 1x1", 24 | []byte("farbfeld\000\000\000\001\000\000\000\001rRgGbBaA"), 25 | []byte("rRgGbBaA"), 26 | 1, 1, 27 | }, 28 | } 29 | 30 | func TestDecodeImage(t *testing.T) { 31 | for _, test := range decodeTests { 32 | r := bytes.NewBuffer(test.input) 33 | img, err := Decode(r) 34 | if err != nil { 35 | t.Errorf("test %s: err not nil: %v", test.descr, err) 36 | continue 37 | } 38 | nrgba64, ok := img.(*image.NRGBA64) 39 | if !ok { 40 | t.Errorf("%s: decoded image has wrong type", test.descr) 41 | continue 42 | } 43 | if dx, dy := img.Bounds().Dx(), img.Bounds().Dy(); dx != test.w || dy != test.h { 44 | t.Errorf("%s: decoded image has wrong size: %d x %d | expected: %d x %d", 45 | test.descr, dx, dy, test.w, test.h) 46 | } 47 | if bytes.Compare(test.img, nrgba64.Pix) != 0 { 48 | t.Errorf("%s: image data differs", test.descr) 49 | } 50 | } 51 | } 52 | 53 | func TestDecodeImageBadHeader(t *testing.T) { 54 | r := bytes.NewBuffer([]byte("farbfeld\000\000\000\000\000\000\000")) 55 | img, err := Decode(r) 56 | if err != io.ErrUnexpectedEOF { 57 | t.Errorf("returned error is wrong, expected: %#v | got: %#v", io.ErrUnexpectedEOF, err) 58 | } 59 | if img != nil { 60 | t.Errorf("returned image is not nil") 61 | } 62 | } 63 | 64 | func TestDecodeImageBad(t *testing.T) { 65 | r := bytes.NewBuffer([]byte("farbfeld\000\000\000\001\000\000\000\001rgb")) 66 | _, err := Decode(r) 67 | if err != io.ErrUnexpectedEOF { 68 | t.Errorf("returned error is wrong, expected: %#v | got: %#v", io.ErrUnexpectedEOF, err) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /encode_test.go: -------------------------------------------------------------------------------- 1 | package imagefile 2 | 3 | import ( 4 | "bytes" 5 | "image" 6 | "image/color" 7 | "testing" 8 | ) 9 | 10 | func TestEncodeEmptyImage(t *testing.T) { 11 | img := image.NewRGBA64(image.Rect(0, 0, 0, 0)) 12 | w := new(bytes.Buffer) 13 | err := Encode(w, img) 14 | if err != nil { 15 | t.Errorf("err is not nil: %v", err) 16 | } 17 | if 0 != bytes.Compare(w.Bytes(), []byte("farbfeld\000\000\000\000\000\000\000\000")) { 18 | t.Errorf("encoded image differs") 19 | } 20 | } 21 | 22 | func TestEncodeSmallImage(t *testing.T) { 23 | img := image.NewNRGBA64(image.Rect(0, 0, 1, 1)) 24 | img.Pix = []byte("aAbBcCdD") 25 | w := new(bytes.Buffer) 26 | err := Encode(w, img) 27 | if err != nil { 28 | t.Errorf("err is not nil: %v", err) 29 | } 30 | if 0 != bytes.Compare(w.Bytes(), []byte("farbfeld\000\000\000\001\000\000\000\001aAbBcCdD")) { 31 | t.Errorf("encoded image differs") 32 | } 33 | } 34 | 35 | func TestEncodeSubImage(t *testing.T) { 36 | img := image.NewNRGBA64(image.Rect(0, 0, 4, 4)) 37 | for y := 0; y < 4; y++ { 38 | for x := 0; x < 4; x++ { 39 | c := uint16(y*4*4 + x*4) 40 | img.SetNRGBA64(x, y, color.NRGBA64{c, c + 1, c + 2, c + 3}) 41 | } 42 | } 43 | w := new(bytes.Buffer) 44 | err := Encode(w, img.SubImage(image.Rect(1, 1, 3, 3))) 45 | if err != nil { 46 | t.Errorf("err is not nil: %v", err) 47 | } 48 | if 0 != bytes.Compare(w.Bytes(), []byte("farbfeld\000\000\000\002\000\000\000\002"+ 49 | "\x00\x14\x00\x15\x00\x16\x00\x17\x00\x18\x00\x19\x00\x1a\x00\x1b"+ 50 | "\x00\x24\x00\x25\x00\x26\x00\x27\x00\x28\x00\x29\x00\x2a\x00\x2b")) { 51 | t.Errorf("encoded image differs") 52 | } 53 | } 54 | 55 | func TestEncodeNRGBA(t *testing.T) { 56 | img := image.NewNRGBA(image.Rect(0, 0, 1, 1)) 57 | img.Pix = []byte{0x00, 0x55, 0xa0, 0xff} 58 | w := new(bytes.Buffer) 59 | err := Encode(w, img) 60 | if err != nil { 61 | t.Errorf("err is not nil: %v", err) 62 | } 63 | if 0 != bytes.Compare(w.Bytes(), []byte("farbfeld\000\000\000\001\000\000\000\001"+ 64 | "\x00\x00\x55\x55\xa0\xa0\xff\xff")) { 65 | t.Errorf("encoded image differs") 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/ff2png/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "github.com/hullerob/go.farbfeld" 5 | "image" 6 | "image/png" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | if len(os.Args) != 1 { 12 | usage() 13 | os.Exit(1) 14 | } 15 | m, _, err := image.Decode(os.Stdin) 16 | if err != nil { 17 | os.Stderr.WriteString(err.Error() + "\n") 18 | os.Exit(1) 19 | } 20 | err = png.Encode(os.Stdout, m) 21 | os.Stdout.Sync() 22 | if err != nil { 23 | os.Stderr.WriteString(err.Error() + "\n") 24 | os.Exit(1) 25 | } 26 | } 27 | 28 | func usage() { 29 | os.Stderr.WriteString("usage: ff2png\n") 30 | } 31 | -------------------------------------------------------------------------------- /examples/img2ff/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/hullerob/go.farbfeld" 5 | _ "golang.org/x/image/bmp" 6 | _ "golang.org/x/image/tiff" 7 | _ "golang.org/x/image/webp" 8 | "image" 9 | _ "image/gif" 10 | _ "image/jpeg" 11 | _ "image/png" 12 | "os" 13 | ) 14 | 15 | func main() { 16 | if len(os.Args) != 1 { 17 | usage() 18 | os.Exit(1) 19 | } 20 | m, _, err := image.Decode(os.Stdin) 21 | if err != nil { 22 | os.Stderr.WriteString(err.Error() + "\n") 23 | os.Exit(1) 24 | } 25 | err = imagefile.Encode(os.Stdout, m) 26 | os.Stdout.Sync() 27 | if err != nil { 28 | os.Stderr.WriteString(err.Error() + "\n") 29 | os.Exit(1) 30 | } 31 | } 32 | 33 | func usage() { 34 | os.Stderr.WriteString("usage: img2ff\n") 35 | } 36 | -------------------------------------------------------------------------------- /farbfeld.go: -------------------------------------------------------------------------------- 1 | // See LICENSE file for copyright and license details. 2 | 3 | // Package farbfeld implements a farbfeld decoder and encoder. 4 | // 5 | // The farbfeld specification can be found at http://git.suckless.org/farbfeld/ 6 | package imagefile 7 | 8 | import ( 9 | "encoding/binary" 10 | "image" 11 | "image/color" 12 | "io" 13 | ) 14 | 15 | const ( 16 | farbfeldHeader string = "farbfeld????????" 17 | ) 18 | 19 | func init() { 20 | image.RegisterFormat("farbfeld", farbfeldHeader, Decode, DecodeConfig) 21 | } 22 | 23 | // Decode reads a farbfeld from r and returns it as image.NRGBA64. 24 | func Decode(r io.Reader) (image.Image, error) { 25 | cfg, err := DecodeConfig(r) 26 | if err != nil { 27 | return nil, err 28 | } 29 | img := image.NewNRGBA64(image.Rect(0, 0, cfg.Width, cfg.Height)) 30 | // image.NRGBA64 is big endian, so is farbfeld → just copy bytes 31 | _, err = io.ReadFull(r, img.Pix) 32 | return img, err 33 | } 34 | 35 | // DecodeConfig returns dimensions of farbfeld image. 36 | func DecodeConfig(r io.Reader) (image.Config, error) { 37 | var cfg image.Config 38 | buff := make([]uint8, 16) 39 | _, err := io.ReadFull(r, buff) 40 | cfg.ColorModel = color.NRGBA64Model 41 | be := binary.BigEndian 42 | cfg.Width = int(be.Uint32(buff[8:12])) 43 | cfg.Height = int(be.Uint32(buff[12:16])) 44 | return cfg, err 45 | } 46 | 47 | // Encode writes m to w in farbfeld format. If m is not image.NRGBA64, 48 | // it will be converted lossily. 49 | func Encode(w io.Writer, m image.Image) error { 50 | header := []uint8(farbfeldHeader) 51 | be := binary.BigEndian 52 | width := m.Bounds().Dx() 53 | height := m.Bounds().Dy() 54 | be.PutUint32(header[8:12], uint32(width)) 55 | be.PutUint32(header[12:16], uint32(height)) 56 | _, err := w.Write(header) 57 | if err != nil { 58 | return err 59 | } 60 | switch img := m.(type) { 61 | case *image.NRGBA64: 62 | pix := img.Pix 63 | for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ { 64 | _, err = w.Write(pix[:width*8]) 65 | if err != nil { 66 | return err 67 | } 68 | pix = pix[img.Stride:] 69 | } 70 | default: 71 | pix := make([]uint8, width*8) 72 | for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ { 73 | encodeLine(pix, img, y) 74 | _, err = w.Write(pix) 75 | if err != nil { 76 | return err 77 | } 78 | } 79 | } 80 | return err 81 | } 82 | 83 | func encodeLine(pix []uint8, m image.Image, y int) { 84 | be := binary.BigEndian 85 | for x := m.Bounds().Min.X; x < m.Bounds().Max.X; x++ { 86 | c := color.NRGBA64Model.Convert(m.At(x, y)).(color.NRGBA64) 87 | be.PutUint16(pix, c.R) 88 | pix = pix[2:] 89 | be.PutUint16(pix, c.G) 90 | pix = pix[2:] 91 | be.PutUint16(pix, c.B) 92 | pix = pix[2:] 93 | be.PutUint16(pix, c.A) 94 | pix = pix[2:] 95 | } 96 | } 97 | --------------------------------------------------------------------------------