Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow read/write 16bit files #3

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 28 additions & 14 deletions reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
// per channel.
//
// To only be able to load pnm images using image.Decode, use
//
// import _ "github.com/jbuchbinder/gopnm"
//
// Not implemented are:
// - Writing pnm files in raw format.
// - Writing images with 16 bits per channel.
// - Writing images with a custom Maxvalue.
// - Reading/and writing PAM images.
// - Writing pnm files in raw format.
// - Reading/and writing PAM images.
//
// (I would be happy to accept patches for these.)
//
// Specifications can be found at http://netpbm.sourceforge.net/doc/#formats.
Expand Down Expand Up @@ -218,19 +218,33 @@ func decodeRawRGB(r io.Reader, c PNMConfig) (image.Image, error) {
}

func decodeRawRGB64(r io.Reader, c PNMConfig) (image.Image, error) {
m := image.NewRGBA(image.Rect(0, 0, c.Width, c.Height))
m := image.NewRGBA64(image.Rect(0, 0, c.Width, c.Height))
count := len(m.Pix)

for i := 0; i < count; i += 8 {
pixel := m.Pix[i : i+6]
m.Pix[i+6] = 0xff
m.Pix[i+7] = 0xff
if c.Maxval < 256 {
for i := 0; i+8 <= count; i += 8 {
pixel := m.Pix[i : i+6 : i+6]
m.Pix[i+6] = 0xff
m.Pix[i+7] = 0xff

if _, err := io.ReadFull(r, pixel); err != nil {
return nil, err
}
}
return m, nil
}

for i := 0; i+16 <= count; i += 16 {
pixel := m.Pix[i : i+12 : i+12]
m.Pix[i+12] = 0xff
m.Pix[i+13] = 0xff
m.Pix[i+14] = 0xff
m.Pix[i+15] = 0xff
if _, err := io.ReadFull(r, pixel); err != nil {
return nil, err
}
}

}
return m, nil
}

Expand All @@ -241,10 +255,10 @@ func decodePAM(r io.Reader, c PNMConfig) (image.Image, error) {
// Decode reads a PNM image from r and returns it as an image.Image.
//
// The type of Image returned depends on the PNM contents:
// - PBM: image.Gray with black = 0 and white = 255
// - PGM: image.Gray or image.Gray16, values as in the file
// - PPM: image.RGBA or image.RGBA64, values as in the file
// - PAM: not supported (yet)
// - PBM: image.Gray with black = 0 and white = 255
// - PGM: image.Gray or image.Gray16, values as in the file
// - PPM: image.RGBA or image.RGBA64, values as in the file
// - PAM: not supported (yet)
func Decode(r io.Reader) (image.Image, error) {
br := bufio.NewReader(r)
c, err := DecodeConfigPNM(br)
Expand Down
72 changes: 57 additions & 15 deletions writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,28 @@ func encodePGM(w io.Writer, m image.Image, maxvalue int) error {

// write raster
cm := color.GrayModel
row := make([]uint8, b.Dx())
setY := func(row []uint8, c color.Color, off int) {
row[off] = cm.Convert(c).(color.Gray).Y
}
var row []uint8
if maxvalue <= 255 {
row = make([]uint8, b.Dx())
} else {
cm = color.Gray16Model
row = make([]uint8, 2*b.Dx())
setY = func(row []uint8, cc color.Color, off int) {
// Each sample is represented in pure binary by either 1 or 2 bytes.
// If the Maxval is less than 256, it is 1 byte. Otherwise, it is 2 bytes.
// The most significant byte is first.
Y := cm.Convert(cc).(color.Gray16).Y
row[off*2] = uint8(Y >> 8)
row[off*2+1] = uint8(Y & 0xff)
}
}

for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
c := cm.Convert(m.At(x, y)).(color.Gray)
row[x-b.Min.X] = c.Y
setY(row, m.At(x, y), x-b.Min.X)
}
if _, err := w.Write(row); err != nil {
return err
Expand All @@ -114,14 +131,32 @@ func encodePPM(w io.Writer, m image.Image, maxvalue int) error {

// write raster
cm := color.RGBAModel
row := make([]uint8, b.Dx()*3)
var row []uint8
set := func(row []uint8, cc color.Color, off int) {
c := cm.Convert(cc).(color.RGBA)
row[off] = c.R
row[off+1] = c.G
row[off+2] = c.B
}
if maxvalue <= 255 {
row = make([]uint8, b.Dx()*3)
} else {
cm = color.RGBA64Model
row = make([]uint8, b.Dx()*3*2)
set = func(row []uint8, cc color.Color, off int) {
c := cm.Convert(cc).(color.RGBA64)
row[off*2] = uint8(c.R >> 8)
row[off*2+1] = uint8(c.R & 0xff)
row[off*2+2] = uint8(c.G >> 8)
row[off*2+3] = uint8(c.G & 0xff)
row[off*2+4] = uint8(c.B >> 8)
row[off*2+5] = uint8(c.B & 0xff)
}
}
for y := b.Min.Y; y < b.Max.Y; y++ {
i := 0
for x := b.Min.X; x < b.Max.X; x++ {
c := cm.Convert(m.At(x, y)).(color.RGBA)
row[i] = c.R
row[i+1] = c.G
row[i+2] = c.B
set(row, m.At(x, y), i)
i += 3
}
if _, err := w.Write(row); err != nil {
Expand All @@ -134,20 +169,27 @@ func encodePPM(w io.Writer, m image.Image, maxvalue int) error {
// Encode writes an image.Image m to io.Writer w in PNM format.
//
// The specific format is determined by pnmType, this can be one of:
// - pnm.PBM (black/white)
// - pnm.PGM (grayscale)
// - pnm.PPM (RGB)
// - pnm.PBM (black/white)
// - pnm.PGM (grayscale)
// - pnm.PPM (RGB)
//
// The image m is converted if necessary.
// Note that PGM/PPM always use 8 bits per channel at the moment and that
// maxvalue is always 255.
func Encode(w io.Writer, m image.Image, pnmType int) error {
switch pnmType {
case PBM:
return encodePBM(w, m)
case PGM:
return encodePGM(w, m, 255)
maxint := 255
if m.ColorModel() == color.Gray16Model {
maxint = 65535
}
return encodePGM(w, m, maxint)
case PPM:
return encodePPM(w, m, 255)
maxint := 255
if m.ColorModel() == color.RGBA64Model {
maxint = 65535
}
return encodePPM(w, m, maxint)
}
return errors.New("Invalid PNM type specified.")
}