-
Notifications
You must be signed in to change notification settings - Fork 0
/
imretro.go
155 lines (128 loc) · 4.53 KB
/
imretro.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// Package imretro supports encoding and decode retro-style images in the
// imretro format.
package imretro
import (
"fmt"
"image"
"image/color"
"github.com/spenserblack/go-byteutils"
)
// ModeFlag is the type for enabling a feature by setting a flag in the mode
// byte.
type ModeFlag = byte
// PixelBitsIndex is the "index" (from the right) of the bits in the mode byte
// that signify the number of bits each pixel needs, and also the number of
// available colors.
const pixelBitsIndex byte = 6
// PixelMode is the type for managing the number of bits per pixel.
type PixelMode = ModeFlag
// Mode flags for picking the number of bits each pixel will have.
const (
OneBit PixelMode = iota << pixelBitsIndex
TwoBit
EightBit
)
// PaletteIndex is the "index" (from the right) of the bit in the mode byte that
// signifies if there is an in-file palette.
const paletteIndex byte = 5
// WithPalette can be used with a union with the bit count when setting the
// header.
const WithPalette byte = 1 << paletteIndex
// ColorChannelIndex is the "index" (from the right) of the bit in the mode byte
// that signifies the number of color channels in the palette.
const colorChannelIndex byte = 1
// Feature flags for setting the number of color channels each color will have
// in the palette. Ignored if the WithPalette flag is not set.
const (
Grayscale ModeFlag = iota << colorChannelIndex
RGB
RGBA
)
// ColorAccuracyIndex is the "index" (from the right) of the bit in the mode
// byte that signifies if the color accuracy that should be used.
const colorAccuracyIndex byte = 0
// EightBitColors sets the mode byte to signify that each color channel should
// use a byte, instead of 2 bits for each color channel.
const EightBitColors byte = 1 << colorAccuracyIndex
// MaximumDimension is the maximum size of an image's boundary in the imretro
// format.
const MaximumDimension int = 0xFFF
// UnsupportedBitModeError should be returned when an unexpected number
// of bits is received.
type UnsupportedBitModeError byte
// DimensionsTooLargeError should be returned when an encoded image would
// have boundaries that are not valid in the encoding.
type DimensionsTooLargeError int
// IsBitCountSupported checks if the bit count is supported by the imretro
// format.
func IsBitCountSupported(count PixelMode) bool {
for _, bits := range []PixelMode{OneBit, TwoBit, EightBit} {
if count == bits {
return true
}
}
return false
}
// Error converts to an error string.
func (e UnsupportedBitModeError) Error() string {
return fmt.Sprintf("Unsupported bit count byte: %#b", byte(e))
}
// Error makes a string representation of the too-large error.
func (e DimensionsTooLargeError) Error() string {
return fmt.Sprintf("Dimensions too large for 16-bit number: %d", int(e))
}
// Image is an image decoded from the imretro format.
type Image interface {
image.PalettedImage
// Palette gets the palette of the image.
Palette() color.Palette
// PixelMode returns the pixel mode of the image.
PixelMode() PixelMode
// BitsPerPixel returns the number of bits used for each pixel.
BitsPerPixel() int
}
// ImretroImage is the helper struct for imretro images.
type imretroImage struct {
config image.Config
pixels []byte
}
// PixelMode returns the pixel mode.
func (i imretroImage) PixelMode() PixelMode {
return i.ColorModel().(ColorModel).PixelMode()
}
// BitsPerPixel returns the number of bits used for each pixel.
func (i imretroImage) BitsPerPixel() int {
return i.ColorModel().(ColorModel).BitsPerPixel()
}
// ColorModel returns the Image's color model.
func (i imretroImage) ColorModel() color.Model {
return i.config.ColorModel
}
// Bounds returns the boundaries of the image.
func (i imretroImage) Bounds() image.Rectangle {
return image.Rect(0, 0, i.config.Width, i.config.Height)
}
// ColorIndexAt converts the x/y coordinates of a pixel to the index in the
// palette.
func (i imretroImage) ColorIndexAt(x, y int) uint8 {
index := (y * i.config.Width) + x
bitsPerPixel := i.BitsPerPixel()
offset := index * bitsPerPixel
byteIndex := offset / 8
bitIndex := byte(offset % 8)
b := i.pixels[byteIndex]
bit := byteutils.SliceL(b, bitIndex, bitIndex+byte(bitsPerPixel))
return uint8(bit)
}
// At returns the color at the given pixel.
func (i imretroImage) At(x, y int) color.Color {
if !image.Pt(x, y).In(i.Bounds()) {
return noColor
}
model := i.ColorModel().(ColorModel)
return model[i.ColorIndexAt(x, y)]
}
// Palette returns the color model as a palette for the image.
func (i imretroImage) Palette() color.Palette {
return color.Palette(i.ColorModel().(ColorModel))
}