Skip to content

Commit 961b282

Browse files
committed
Refactored most of the code to fix a bug with lossy compression using Image.At(). There are still bugs, but less.
1 parent 7de9d24 commit 961b282

File tree

13 files changed

+424
-116
lines changed

13 files changed

+424
-116
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@
1616

1717

1818
.idea/*
19-
*.o.*
19+
*.o.*
20+
out*.txt

.testFiles/empty.txt

Whitespace-only changes.

.testFiles/test10.png

1.58 KB
Loading

.testFiles/test11.png

1.59 KB
Loading

.testFiles/test12.png

139 KB
Loading

.testFiles/test13.png

1.58 KB
Loading

.testFiles/test8.png

2.1 KB
Loading

.testFiles/test9.png

1.55 KB
Loading

imgio/imgio.go

Lines changed: 199 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,47 @@ import (
77
"image/color"
88
"image/png"
99
"io"
10+
"math"
1011
"os"
1112
)
1213

14+
//TODO: Potentially change this to type Pixel []uint16
1315
type Pixel struct {
14-
R uint8
15-
G uint8
16-
B uint8
17-
A uint8
16+
Channels []uint16
1817
}
1918

20-
func LoadImage(imgPath string) (pixels [][]Pixel, e error) {
21-
image.RegisterFormat("png", "png", png.Decode, png.DecodeConfig)
19+
type FmtInfo struct {
20+
Model color.Model
21+
ChannelsPerPix uint8
22+
BitsPerChannel uint8
23+
}
24+
25+
func (info *FmtInfo) BytesPerChannel() uint8 {
26+
return uint8(math.Ceil(float64(info.BitsPerChannel / bitsPerByte)))
27+
}
28+
29+
func (info *FmtInfo) String() string {
30+
return fmt.Sprintf("{%v %d %d}", colorModelToStr(info.Model), info.ChannelsPerPix, info.BitsPerChannel)
31+
}
32+
33+
type ImgInfo struct {
34+
W, H uint
35+
Format FmtInfo
36+
}
37+
38+
const (
39+
bitsPerByte uint8 = 8
40+
pngHeader string = "\x89PNG\r\n\x1a\n"
41+
)
42+
43+
type unknownColourModelError struct {}
44+
45+
func (e unknownColourModelError) Error() string {
46+
return "The colour model of the provided Image is unknown."
47+
}
48+
49+
func LoadImage(imgPath string) (pixels *[]Pixel, info ImgInfo, e error) {
50+
image.RegisterFormat("png", pngHeader, png.Decode, png.DecodeConfig)
2251

2352
imgFile, err := os.Open(imgPath)
2453

@@ -27,68 +56,204 @@ func LoadImage(imgPath string) (pixels [][]Pixel, e error) {
2756
return
2857
}
2958

30-
defer imgFile.Close()
59+
defer func() {
60+
if err = imgFile.Close(); err != nil {
61+
fmt.Println("Error closing the file:", err.Error())
62+
}
63+
}()
3164

32-
pixels, err = readPixels(imgFile)
65+
pixels, info, err = readPixels(imgFile)
3366
//fmt.Println(pixels)
3467

3568
if err != nil {
36-
fmt.Println("The image couldn't be decoded.", err.Error())
69+
fmt.Println("The image couldn't be decoded:", err.Error())
3770
return
3871
}
3972

4073
return
4174
}
4275

43-
func readPixels(imgFile io.Reader) (pixels [][]Pixel, e error) {
76+
func readPixels(imgFile io.Reader) (pixels *[]Pixel, info ImgInfo, e error) {
4477
img, _, err := image.Decode(imgFile)
78+
//img, err := png.Decode(imgFile)
4579

4680
if err != nil {
47-
return nil, err
81+
return nil, ImgInfo{}, err
4882
}
4983

5084
dims := img.Bounds()
5185
w, h := dims.Max.X, dims.Max.Y
52-
//TODO: Potentially check for 0x0 dims
5386

54-
for y := 0; y < h; y++ {
55-
row := make([]Pixel, 0, w)
56-
for x := 0; x < w; x++ {
57-
row = append(row, rgbaToPixel(img.At(x, y).RGBA()))
58-
}
59-
pixels = append(pixels, row)
60-
}
87+
info = ImgInfo{W: uint(w), H:uint(h)}
6188

89+
fmt.Println("Colour model:", colorModelToStr(img.ColorModel()))
90+
//Handle each image type independently, parsing out the pixel channel values
91+
switch img.(type) {
92+
case *image.Alpha16:
93+
info.Format = FmtInfo{color.Alpha16Model, 4, 16}
94+
simg := img.(*image.Alpha16)
95+
pixels = imgPixToPixels(&simg.Pix, info.Format)
96+
case *image.Alpha:
97+
info.Format = FmtInfo{color.AlphaModel, 4, 8}
98+
simg := img.(*image.Alpha)
99+
pixels = imgPixToPixels(&simg.Pix, info.Format)
100+
case *image.CMYK:
101+
info.Format = FmtInfo{color.CMYKModel, 4, 8}
102+
simg := img.(*image.CMYK)
103+
pixels = imgPixToPixels(&simg.Pix, info.Format)
104+
case *image.Gray16:
105+
info.Format = FmtInfo{color.Gray16Model, 4, 16}
106+
simg := img.(*image.Gray16)
107+
pixels = imgPixToPixels(&simg.Pix, info.Format)
108+
case *image.Gray:
109+
info.Format = FmtInfo{color.GrayModel, 4, 8}
110+
simg := img.(*image.Gray)
111+
pixels = imgPixToPixels(&simg.Pix, info.Format)
112+
case *image.NRGBA64:
113+
info.Format = FmtInfo{color.NRGBA64Model, 4, 16}
114+
simg := img.(*image.NRGBA64)
115+
pixels = imgPixToPixels(&simg.Pix, info.Format)
116+
case *image.NRGBA:
117+
info.Format = FmtInfo{color.NRGBAModel, 4, 8}
118+
simg := img.(*image.NRGBA)
119+
pixels = imgPixToPixels(&simg.Pix, info.Format)
120+
case *image.RGBA64:
121+
info.Format = FmtInfo{color.RGBA64Model, 4, 16}
122+
simg := img.(*image.RGBA64)
123+
pixels = imgPixToPixels(&simg.Pix, info.Format)
124+
case *image.RGBA:
125+
info.Format = FmtInfo{color.RGBAModel, 4, 8}
126+
simg := img.(*image.RGBA)
127+
pixels = imgPixToPixels(&simg.Pix, info.Format)
128+
default:
129+
return nil, info, unknownColourModelError{}
130+
}
62131
return
63132
}
64133

65-
func WriteImage(pixels *[][]Pixel, outPath string) {
66-
w, h := len((*pixels)[0]), len(*pixels)
134+
func imgPixToPixels(pix *[]uint8, info FmtInfo) *[]Pixel {
135+
bytesPerChannel := info.BitsPerChannel / bitsPerByte
136+
pixels := make([]Pixel, len(*pix) / int(info.ChannelsPerPix * bytesPerChannel))
137+
for i := range pixels {
138+
pixels[i] = Pixel{Channels:make([]uint16, info.ChannelsPerPix)}
139+
for j := uint8(0); j < info.ChannelsPerPix; j++ {
140+
//Image raw Pix arrays store multi-byte channel values in big-endian format
141+
//across separate indices (https://golang.org/src/image/image.go?s=8222:8528#L380)
142+
for k := uint8(0); k < info.BytesPerChannel(); k++ {
143+
pixels[i].Channels[j] <<= bitsPerByte
144+
pixels[i].Channels[j] += uint16((*pix)[i * int(info.ChannelsPerPix * bytesPerChannel) + int(j * bytesPerChannel)])
145+
}
146+
}
147+
}
148+
return &pixels
149+
}
67150

68-
img := image.NewRGBA(image.Rectangle{Max: image.Point{w, h}})
69-
for y := 0; y < h; y++ {
70-
for x := 0; x < w; x++ {
71-
img.Set(x, y, pixelToRgba((*pixels)[y][x]))
151+
func updatePixWithPixels(pix *[]uint8, pixels *[]Pixel, info FmtInfo) {
152+
bytes := info.BytesPerChannel()
153+
for i := range *pixels {
154+
for j := range (*pixels)[i].Channels {
155+
for k := uint8(0); k < bytes; k++ {
156+
(*pix)[(i * int(info.ChannelsPerPix) + j) * int(bytes) + int(k)] =
157+
uint8(ReadFrom((*pixels)[i].Channels[j], (bytes - 1 - k) * bitsPerByte, bitsPerByte))
158+
}
72159
}
73160
}
161+
}
162+
163+
func WriteImage(pixels *[]Pixel, info ImgInfo, outPath string) {
164+
var img image.Image
165+
switch info.Format.Model {
166+
case color.Alpha16Model:
167+
simg := image.NewAlpha16(image.Rectangle{Max: image.Point{int(info.W), int(info.H)}})
168+
updatePixWithPixels(&simg.Pix, pixels, info.Format)
169+
img = simg
170+
case color.AlphaModel:
171+
simg := image.NewAlpha(image.Rectangle{Max: image.Point{int(info.W), int(info.H)}})
172+
updatePixWithPixels(&simg.Pix, pixels, info.Format)
173+
img = simg
174+
case color.CMYKModel:
175+
simg := image.NewCMYK(image.Rectangle{Max: image.Point{int(info.W), int(info.H)}})
176+
updatePixWithPixels(&simg.Pix, pixels, info.Format)
177+
img = simg
178+
case color.Gray16Model:
179+
simg := image.NewGray16(image.Rectangle{Max: image.Point{int(info.W), int(info.H)}})
180+
updatePixWithPixels(&simg.Pix, pixels, info.Format)
181+
img = simg
182+
case color.GrayModel:
183+
simg := image.NewGray(image.Rectangle{Max: image.Point{int(info.W), int(info.H)}})
184+
updatePixWithPixels(&simg.Pix, pixels, info.Format)
185+
img = simg
186+
case color.NRGBA64Model:
187+
simg := image.NewNRGBA64(image.Rectangle{Max: image.Point{int(info.W), int(info.H)}})
188+
updatePixWithPixels(&simg.Pix, pixels, info.Format)
189+
img = simg
190+
case color.NRGBAModel:
191+
simg := image.NewNRGBA(image.Rectangle{Max: image.Point{int(info.W), int(info.H)}})
192+
updatePixWithPixels(&simg.Pix, pixels, info.Format)
193+
img = simg
194+
case color.RGBA64Model:
195+
simg := image.NewRGBA64(image.Rectangle{Max: image.Point{int(info.W), int(info.H)}})
196+
updatePixWithPixels(&simg.Pix, pixels, info.Format)
197+
img = simg
198+
case color.RGBAModel:
199+
simg := image.NewRGBA(image.Rectangle{Max: image.Point{int(info.W), int(info.H)}})
200+
updatePixWithPixels(&simg.Pix, pixels, info.Format)
201+
img = simg
202+
default:
203+
fmt.Println("Unknown image format.")
204+
return
205+
}
74206

75207
f, err := os.Create(outPath)
76208
if err != nil {
77209
fmt.Printf("There was an error creating the file '%v': %v\n", outPath, err.Error())
78210
}
211+
defer func() {
212+
if err = f.Close(); err != nil {
213+
fmt.Println("Error closing the file:", err.Error())
214+
}
215+
}()
79216

80-
err = png.Encode(f, img)
217+
//TODO: Add support for additional format exports
218+
encoder := png.Encoder{CompressionLevel:png.BestCompression}
219+
err = encoder.Encode(f, img)
81220
if err != nil {
82221
fmt.Printf("There was an error encoding the image to the new file: %v\n", err.Error())
83222
}
84223
}
85224

86-
func rgbaToPixel(r, g, b, a uint32) Pixel {
87-
return Pixel{uint8(r), uint8(g), uint8(b), uint8(a)}
88-
}
225+
/*func pixelToRgba(pix Pixel) color.Color {
226+
//fmt.Println(pix)
227+
return color.NRGBA{pix.R, pix.G, pix.B, pix.A}
228+
}*/
89229

90-
func pixelToRgba(pix Pixel) color.Color {
91-
return color.RGBA{pix.R, pix.G, pix.B, pix.A}
230+
func colorModelToStr(model color.Model) string {
231+
switch model {
232+
case color.Alpha16Model:
233+
return "Alpha16"
234+
case color.AlphaModel:
235+
return "Alpha"
236+
case color.CMYKModel:
237+
return "CMYK"
238+
case color.Gray16Model:
239+
return "Gray16"
240+
case color.GrayModel:
241+
return "Gray"
242+
case color.NRGBA64Model:
243+
return "NRGBA64"
244+
case color.NRGBAModel:
245+
return "NRGBA"
246+
case color.RGBA64Model:
247+
return "RGBA64"
248+
case color.RGBAModel:
249+
return "RGBA"
250+
case color.NYCbCrAModel:
251+
return "NYCbCrA"
252+
case color.YCbCrModel:
253+
return "YCbCr"
254+
default:
255+
return "<Unknown>"
256+
}
92257
}
93258

94259
//PCB = Pixel, Channel, Bit #
@@ -135,14 +300,14 @@ func HashPatternFile(patternPath string) int64 {
135300

136301
//Bit manipulation functions
137302

138-
func GetMask(index, size uint8) uint8 {
303+
func GetMask(index, size uint8) uint16 {
139304
return ((1 << size) - 1) << index
140305
}
141306

142-
func ReadFrom(data uint8, index, size uint8) uint8 {
307+
func ReadFrom(data uint16, index, size uint8) uint16 {
143308
return (data & GetMask(index, size)) >> index
144309
}
145310

146-
func WriteTo(data uint8, index, size uint8, value uint8) uint8 {
311+
func WriteTo(data uint16, index, size uint8, value uint16) uint16 {
147312
return (data & (^GetMask(index, size))) | (value << index)
148313
}

main.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package main
22

33
import (
44
"flag"
5-
"fmt"
5+
//"fmt"
66
"github.com/zedseven/steg/op"
77
//"os"
88
)
@@ -16,8 +16,8 @@ func main() {
1616
outPath := flag.String("out", "", "The filepath to write the steg image to")
1717
patternPath := flag.String("pattern", "", "The filepath to the file used for the pattern hash")
1818
bits := flag.Uint("bits", 1, "The number of bits to modify per channel (1-8), at a maximum (working inwards as determined by -msb)")
19-
lsb := flag.Bool("msb", false, "Whether to modify the most or least-significant bit - mostly for debugging")
20-
encodeAlpha := flag.Bool("alpha", true, "Whether to touch the alpha (transparency) channel")
19+
msb := flag.Bool("msb", false, "Whether to modify the most-significant bits instead - mostly for debugging")
20+
encodeAlpha := flag.Bool("alpha", false, "Whether to touch the alpha (transparency) channel")
2121

2222
/*if len(os.Args) < 2 {
2323
fmt.Println("You have to specify what you want me to do!")
@@ -30,11 +30,9 @@ func main() {
3030
return
3131
}
3232
if !*digToggle {
33-
op.Hide(*imgPath, *filePath, *outPath, *patternPath, uint8(*bits), *encodeAlpha, !*lsb)
33+
op.Hide(*imgPath, *filePath, *outPath, *patternPath, uint8(*bits), *encodeAlpha, !*msb)
3434
} else {
35-
35+
op.Dig(*imgPath, *outPath, *patternPath, uint8(*bits), *encodeAlpha, !*msb)
3636
}
37-
38-
fmt.Println("Hello world! I'm steg. c:")
3937
}
4038

0 commit comments

Comments
 (0)