Skip to content

Commit

Permalink
Merge pull request #30 from mdouchement/hli
Browse files Browse the repository at this point in the history
HLI file format
  • Loading branch information
mdouchement authored Aug 1, 2020
2 parents 141ad3a + de008cd commit 2277354
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 137 deletions.
31 changes: 0 additions & 31 deletions codec/crad/compresser.go

This file was deleted.

63 changes: 0 additions & 63 deletions codec/crad/util.go

This file was deleted.

64 changes: 42 additions & 22 deletions codec/crad/README.md → codec/hli/README.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,74 @@
# CRAD
# HLI

The CRAD is an HDR image format aimed to be simple as possible.
The HLI is an HDR image format aimed to be simple as possible.

File extension `.crad`.
File extension `.hli`.


## General structure

```
#?CRAD\n
{"width":3272,"height":1280,"depth":32,"format":"LogLuv","raster_mode":"separately","compression":"gzip"}\n
+----------------+
| |
| Raster |
| |
+----------------+
```
`[magic number][1-byte header-size length][variable-length header-size][variable-length header][raster]`

### Magic number

The magic number of the the CRAD is `#?CRAD` at the first line.
The magic number of the the HLI is `HLi.v1` at the first line.

### Header

The header is a one-line JSON string at the second line.
CBOR encoded data:
```go
type Header struct {
Width int `cbor:"width"`
Height int `cbor:"height"`
Depth int `cbor:"depth"`
Format string `cbor:"format"`
RasterMode string `cbor:"raster_mode"`
Compression string `cbor:"compression"`
Metadata map[string]string `cbor:"metadata,omitempty"`
}
```
> See consts.go for possible values for each field.
- `metadata` is optional

Default options:
```go
Mode6 = &hli.Header{
Depth: 32,
Format: hli.FormatLogLuv,
RasterMode: hli.RasterModeSeparately,
Compression: hli.CompressionZstd,
}
```

### Raster

The raster contains all the pixels of the image.

#### Compression

The raster is compressed with the specified algorithm in the JSON header (typically: `gzip`)
The raster is compressed with the specified algorithm in the header (typically: `gzip`)

Supported compression:
- `gzip`
- `zstd` (default)

#### Format

- RGBE and XYZE
- `RGBE` and `XYZE`

These formats are based on the Radiance RGBE enconding.
These formats are based on the Radiance RGBE encoding.
A pixel is stored on a 4-byte representation where three 8-bit mantissas shared a common 8-bit exponent.
It offers a very compact storage of 32-bit floating points.
The net result is a format that has an absolute accuracy of about 1%, covering a range of over 76 orders of magnitude.

- RGB and XYZ
- `RGB` and `XYZ`

These formats are based on the representation of a 32-bit floating points in bytes.
A pixel is stored on a 12-byte representation where a channel is coded on 4 bytes in little endian order.
It offers a great absolute accuracy.

- LogLuv (used as default format)
- `LogLuv` (used as default format)

This format is based on the LogLuv Encoding for Full Gamut.
A pixel is stored on a 4-byte representation where a channel is coded on 4 bytes in.
Expand All @@ -62,7 +82,7 @@ A pixel is stored on a 4-byte representation where a channel is coded on 4 bytes
S Le ue ve
# CRAD LogLuv representation
# HLI LogLuv representation
8 8 8 8
|--------+---------|--------+--------|
Expand All @@ -76,7 +96,7 @@ It offers a great compression and with an absolute accuracy of about 0.3%, cover

Uncompressed `Raster` is stored in several modes:

- Normal mode
- `normal` mode

Each pixels' bytes are stored in contiguous order.

Expand All @@ -100,7 +120,7 @@ SLeleueveSLeleueveSLeleueve
SLeleueveSLeleueveSLeleueve
```

- Separately mode
- `separately` mode

The color channels are stored separately in order to improve the compression ratio.

Expand Down
61 changes: 61 additions & 0 deletions codec/hli/compresser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package hli

import (
"compress/gzip"
"io"

"github.com/klauspost/compress/zstd"
)

type compresserWriter interface {
io.WriteCloser
Flush() error
}

func newCompresserWriter(w io.Writer, h *Header) (c compresserWriter) {
var err error

switch h.Compression {
case CompressionGzip:
c = gzip.NewWriter(w)
case CompressionZstd:
c, err = zstd.NewWriter(w)
}

if err != nil {
panic(err) // Should never occurred
}

return
}

func newCompresserReader(r io.Reader, h *Header) (c io.ReadCloser) {
var err error

switch h.Compression {
case CompressionGzip:
c, err = gzip.NewReader(r)
case CompressionZstd:
c, err = newzstdreader(r)
}

if err != nil {
panic(err) // Should never occurred
}

return
}

type zstdreader struct {
*zstd.Decoder
}

func newzstdreader(r io.Reader) (*zstdreader, error) {
c, err := zstd.NewReader(r)
return &zstdreader{c}, err
}

func (r *zstdreader) Close() error {
r.Decoder.Close()
return nil
}
26 changes: 18 additions & 8 deletions codec/crad/consts.go → codec/hli/consts.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package crad
package hli

const (
header = "#?CRAD"
header = "HLi.v1"
)

const (
Expand All @@ -26,16 +26,19 @@ const (

// CompressionGzip for gzip compression
CompressionGzip = "gzip"
// CompressionZstd for Zstandard compression
CompressionZstd = "zstd"
)

// A Header handles all image properties.
type Header struct {
Width int `json:"width"`
Height int `json:"height"`
Depth int `json:"depth"`
Format string `json:"format"`
RasterMode string `json:"raster_mode"`
Compression string `json:"compression"`
Width int `cbor:"width"`
Height int `cbor:"height"`
Depth int `cbor:"depth"`
Format string `cbor:"format"`
RasterMode string `cbor:"raster_mode"`
Compression string `cbor:"compression"`
Metadata map[string]string `cbor:"metadata,omitempty"`
}

var (
Expand Down Expand Up @@ -74,4 +77,11 @@ var (
RasterMode: RasterModeSeparately,
Compression: CompressionGzip,
}
// Mode6 offers the better and faster compression and quality in LogLuv that covers gamut. (quantization steps: 0.1%)
Mode6 = &Header{
Depth: 32,
Format: FormatLogLuv,
RasterMode: RasterModeSeparately,
Compression: CompressionZstd,
}
)
33 changes: 26 additions & 7 deletions codec/crad/reader.go → codec/hli/reader.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package crad
package hli

import (
"bufio"
"encoding/binary"
"encoding/json"
"image"
"io"

"github.com/fxamacker/cbor/v2"
"github.com/mdouchement/hdr"
"github.com/mdouchement/hdr/format"
"github.com/mdouchement/hdr/hdrcolor"
Expand Down Expand Up @@ -36,20 +36,30 @@ func newDecoder(r io.Reader) (*decoder, error) {
//--------------------------------------//

func (d *decoder) parseHeader() error {
magic, err := readUntil(d.r, '\n')
magic, err := readN(d.r, len(header)) // magic number
if err != nil {
return err
}
if magic != header {
if string(magic) != header {
return FormatError("format not compatible")
}

h, err := readUntil(d.r, '\n')
l, err := readN(d.r, 1) // 1-byte header-size length
if err != nil {
return err
}

if err := json.Unmarshal([]byte(h), d.h); err != nil {
size, err := readN(d.r, int(l[0])) // variable-length header-size
if err != nil {
return err
}

header, err := readN(d.r, bytesToLength(size))
if err != nil {
return err
}

if err := cbor.Unmarshal(header, d.h); err != nil {
return err
}
d.cr = newCompresserReader(d.r, d.h)
Expand Down Expand Up @@ -155,6 +165,15 @@ func (d *decoder) decodeSeparately(dst image.Image, y int, scanline []byte) {
// Reader //
//--------------------------------------//

// DecodeHeader returns the Header without decoding the entire image.
func DecodeHeader(r io.Reader) (Header, error) {
d, err := newDecoder(r)
if err != nil {
return Header{}, err
}
return *d.h, nil
}

// DecodeConfig returns the color model and dimensions of a RGBE image without
// decoding the entire image.
func DecodeConfig(r io.Reader) (image.Config, error) {
Expand Down Expand Up @@ -209,5 +228,5 @@ func Decode(r io.Reader) (img image.Image, err error) {
}

func init() {
image.RegisterFormat("crad", header, Decode, DecodeConfig)
image.RegisterFormat("hli", header, Decode, DecodeConfig)
}
Loading

0 comments on commit 2277354

Please sign in to comment.