diff --git a/devices/lepton/image14bit/gray14.go b/devices/lepton/image14bit/gray14.go new file mode 100644 index 0000000..ed12bcd --- /dev/null +++ b/devices/lepton/image14bit/gray14.go @@ -0,0 +1,82 @@ +// Copyright 2018 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +// Package image14bit implements 14-bit per pixel images. +// +// It is compatible with the image/draw package. +package image14bit + +import ( + "image" + "image/color" + "image/draw" +) + +// Gray14 represents an image of 14-bit values. +type Gray14 struct { + // Pix holds the image's pixels. Each uint16 element represents one 14-bit + // pixel. + Pix []uint16 + // Stride is the Pix stride (in pixels) between vertically adjacent pixels. + Stride int + // Rect is the image's bounds. + Rect image.Rectangle +} + +// NewGray14 returns an initialized Gray14 instance. +func NewGray14(r image.Rectangle) *Gray14 { + w, h := r.Dx(), r.Dy() + pix := make([]uint16, w*h) + return &Gray14{Pix: pix, Stride: w, Rect: r} +} + +// ColorModel implements image.Image. +func (i *Gray14) ColorModel() color.Model { + return Intensity14Model +} + +// Bounds implements image.Image. +func (i *Gray14) Bounds() image.Rectangle { + return i.Rect +} + +// Opaque returns whether the image is fully opaque. +func (i *Gray14) Opaque() bool { + return true +} + +// At implements image.Image. +func (i *Gray14) At(x, y int) color.Color { + return i.Intensity14At(x, y) +} + +// Intensity14At returns the Intensity14 value at a point. +func (i *Gray14) Intensity14At(x, y int) Intensity14 { + if !(image.Point{x, y}.In(i.Rect)) { + return Intensity14(0) + } + offset := i.PixOffset(x, y) + return Intensity14(i.Pix[offset]) +} + +// PixOffset returns the index of the element of Pix that +// corresponds to the pixel at (x, y). +func (i *Gray14) PixOffset(x, y int) int { + return (y-i.Rect.Min.Y)*i.Stride + (x - i.Rect.Min.X) +} + +// Set implements draw.Image. +func (i *Gray14) Set(x, y int, c color.Color) { + i.SetIntensity14(x, y, convertIntensity14(c)) +} + +// SetIntensity14 sets the Intensity14 value for the pixel at (x, y). +func (i *Gray14) SetIntensity14(x, y int, c Intensity14) { + if !(image.Point{x, y}.In(i.Rect)) { + return + } + i.Pix[i.PixOffset(x, y)] = uint16(c) +} + +var _ draw.Image = &Gray14{} diff --git a/devices/lepton/image14bit/gray14_test.go b/devices/lepton/image14bit/gray14_test.go new file mode 100644 index 0000000..6e99982 --- /dev/null +++ b/devices/lepton/image14bit/gray14_test.go @@ -0,0 +1,185 @@ +// Copyright 2018 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +package image14bit + +import ( + "image" + "image/color" + "testing" +) + +func TestNewGray14(t *testing.T) { + data := []struct { + r image.Rectangle + l int + stride int + }{ + // Empty. + { + image.Rect(0, 0, 0, 0), + 0, + 0, + }, + // Empty + { + image.Rect(0, 0, 1, 0), + 0, + 1, + }, + // Empty + { + image.Rect(0, 0, 0, 1), + 0, + 0, + }, + // 1x1 + { + image.Rect(0, 0, 1, 1), + 1, + 1, + }, + // Zero-based + { + image.Rect(0, 0, 9, 17), + 9 * 17, + 9, + }, + // Non-zero-based + { + image.Rect(1, 7, 9, 17), + 8 * 10, + 8, + }, + // Negative X + { + image.Rect(-1, 0, 0, 1), + 1, + 1, + }, + // Negative Y. + { + image.Rect(0, -1, 1, 0), + 1, + 1, + }, + } + for i, line := range data { + img := NewGray14(line.r) + if r := img.Bounds(); r != line.r { + t.Fatalf("#%d: expected %v; actual %v", i, line.r, r) + } + if l := len(img.Pix); l != line.l { + t.Fatalf("#%d: len(img.Pix) expected %v; actual %v for %v", i, line.l, l, line.r) + } + if img.Stride != line.stride { + t.Fatalf("#%d: img.Stride expected %v; actual %v for %v", i, line.stride, img.Stride, line.r) + } + } +} + +func TestAt(t *testing.T) { + img := NewGray14(image.Rect(0, 0, 1, 1)) + img.SetIntensity14(0, 0, Intensity14(16383)) + c := img.At(0, 0) + if g, ok := c.(Intensity14); !ok || g != Intensity14(16383) { + t.Fatal(c, g) + } + // Out of bounds. + c = img.At(0, 1) + if g, ok := c.(Intensity14); !ok || g != Intensity14(0) { + t.Fatal(c, g) + } +} + +func TestIntensity14At(t *testing.T) { + img := NewGray14(image.Rect(0, 0, 1, 1)) + img.SetIntensity14(0, 0, Intensity14(16383)) + if g := img.Intensity14At(0, 0); g != Intensity14(16383) { + t.Fatal(g) + } + // Out of bounds. + if g := img.Intensity14At(0, 1); g != Intensity14(0) { + t.Fatal(g) + } +} + +func TestColorModel(t *testing.T) { + img := NewGray14(image.Rect(0, 0, 1, 8)) + if v := img.ColorModel(); v != Intensity14Model { + t.Fatalf("%s", v) + } + if v := img.ColorModel().Convert(color.NRGBA{0x00, 0x00, 0x00, 0xFF}).(Intensity14); v != Intensity14(0) { + t.Fatalf("%s", v) + } + if v := img.ColorModel().Convert(color.NRGBA{0x7F, 0x7F, 0x7F, 0xFF}).(Intensity14); v != Intensity14(8159) { + t.Fatalf("%s", v) + } + if v := img.ColorModel().Convert(color.NRGBA{0xFF, 0xFF, 0xFF, 0xFF}).(Intensity14); v != Intensity14(16383) { + t.Fatalf("%s", v) + } +} + +func TestOpaque(t *testing.T) { + if !NewGray14(image.Rect(0, 0, 1, 8)).Opaque() { + t.Fatal("image is always opaque") + } +} + +func TestPixOffset(t *testing.T) { + data := []struct { + r image.Rectangle + x, y int + offset int + }{ + { + image.Rect(0, 0, 1, 1), + 0, 0, + 0, + }, + { + image.Rect(0, 0, 1, 8), + 0, 1, + 1, + }, + { + image.Rect(0, 0, 3, 16), + 1, 5, + 16, + }, + { + image.Rect(-1, -1, 3, 16), + 1, 5, + 26, + }, + } + for i, line := range data { + img := NewGray14(line.r) + offset := img.PixOffset(line.x, line.y) + if offset != line.offset { + t.Fatalf("#%d: expected offset:%v, actual offset:%v", i, line.offset, offset) + } + } +} + +func TestSetIntensity14(t *testing.T) { + img := NewGray14(image.Rect(0, 0, 1, 1)) + if img.Pix[0] != 0 { + t.Fatal(img.Pix) + } + if img.SetIntensity14(0, 0, Intensity14(16383)); img.Pix[0] != 16383 { + t.Fatal(img.Pix) + } + if img.SetIntensity14(0, 0, Intensity14(0)); img.Pix[0] != 0 { + t.Fatal(img.Pix) + } +} + +func TestSet(t *testing.T) { + img := NewGray14(image.Rect(0, 0, 1, 1)) + img.Set(0, 0, color.NRGBA{0x80, 0x80, 0x80, 0xFF}) + if img.Pix[0] != 8224 { + t.Fatal(img.Pix) + } +} diff --git a/devices/lepton/image14bit/intensity14.go b/devices/lepton/image14bit/intensity14.go new file mode 100644 index 0000000..1d2660f --- /dev/null +++ b/devices/lepton/image14bit/intensity14.go @@ -0,0 +1,45 @@ +// Copyright 2018 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +package image14bit + +import ( + "image/color" + "strconv" +) + +// Intensity14 is a 14-bit grayscale implementation of color.Color. +// +// Valid range is between 0 and 16383 (inclusive). +type Intensity14 uint16 + +// RGBA returns a grayscale result. +func (g Intensity14) RGBA() (uint32, uint32, uint32, uint32) { + b := uint32(g) & 1 + i := uint32(g)<<2 | b<<1 | b + return i, i, i, 65535 +} + +func (g Intensity14) String() string { + return "Intensity14(" + strconv.Itoa(int(g)) + ")" +} + +// Intensity14Model is the color Model for 14-bit grayscale. +var Intensity14Model = color.ModelFunc(convert) + +func convert(c color.Color) color.Color { + return convertIntensity14(c) +} + +func convertIntensity14(c color.Color) Intensity14 { + switch t := c.(type) { + case Intensity14: + return t + default: + r, g, b, _ := c.RGBA() + // Use the same coefficients as color.GrayModel. + y := (19595*r + 38470*g + 7471*b + 1<<15) >> 18 + return Intensity14(y) + } +} diff --git a/devices/lepton/image14bit/intensity14_test.go b/devices/lepton/image14bit/intensity14_test.go new file mode 100644 index 0000000..1d72ea0 --- /dev/null +++ b/devices/lepton/image14bit/intensity14_test.go @@ -0,0 +1,28 @@ +// Copyright 2018 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +package image14bit + +import ( + "image/color" + "testing" +) + +func TestIntensity14(t *testing.T) { + if r, g, b, a := Intensity14(16383).RGBA(); r != 65535 || g != r || b != r || a != r { + t.Fatal(r, g, b, a) + } + if r, g, b, a := Intensity14(0).RGBA(); r != 0 || g != r || b != r || a != 65535 { + t.Fatal(r, g, b, a) + } + if Intensity14(16383).String() != "Intensity14(16383)" || Intensity14(0).String() != "Intensity14(0)" { + t.Fail() + } + if Intensity14(8192) != convertIntensity14(Intensity14(8192)) { + t.Fatal("failed to convert Intensity14 correctly") + } + if Intensity14(8224) != convertIntensity14(color.NRGBA{0x80, 0x80, 0x80, 0xFF}) { + t.Fatal("failed to convert color.NRGBA correctly") + } +}