OpenDiablo2/d2common/d2fileformats/d2dc6/dc6.go

127 lines
2.6 KiB
Go

package d2dc6
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
)
const (
endOfScanLine = 0x80
maxRunLength = 0x7f
)
type scanlineState int
const (
endOfLine scanlineState = iota
runOfTransparentPixels
runOfOpaquePixels
)
// DC6 represents a DC6 file.
type DC6 struct {
Version int32
Flags uint32
Encoding uint32
Termination []byte // 4 bytes
Directions uint32
FramesPerDirection uint32
FramePointers []uint32 // size is Directions*FramesPerDirection
Frames []*DC6Frame // size is Directions*FramesPerDirection
}
// Load uses restruct to read the binary dc6 data into structs then parses image data from the frame data.
func Load(data []byte) (*DC6, error) {
const (
terminationSize = 4
terminatorSize = 3
)
r := d2datautils.CreateStreamReader(data)
var dc DC6
dc.Version = r.GetInt32()
dc.Flags = r.GetUInt32()
dc.Encoding = r.GetUInt32()
dc.Termination = r.ReadBytes(terminationSize)
dc.Directions = r.GetUInt32()
dc.FramesPerDirection = r.GetUInt32()
frameCount := int(dc.Directions * dc.FramesPerDirection)
dc.FramePointers = make([]uint32, frameCount)
for i := 0; i < frameCount; i++ {
dc.FramePointers[i] = r.GetUInt32()
}
dc.Frames = make([]*DC6Frame, frameCount)
for i := 0; i < frameCount; i++ {
frame := &DC6Frame{
Flipped: r.GetUInt32(),
Width: r.GetUInt32(),
Height: r.GetUInt32(),
OffsetX: r.GetInt32(),
OffsetY: r.GetInt32(),
Unknown: r.GetUInt32(),
NextBlock: r.GetUInt32(),
Length: r.GetUInt32(),
}
frame.FrameData = r.ReadBytes(int(frame.Length))
frame.Terminator = r.ReadBytes(terminatorSize)
dc.Frames[i] = frame
}
return &dc, nil
}
// DecodeFrame decodes the given frame to an indexed color texture
func (d *DC6) DecodeFrame(frameIndex int) []byte {
frame := d.Frames[frameIndex]
indexData := make([]byte, frame.Width*frame.Height)
x := 0
y := int(frame.Height) - 1
offset := 0
loop: // this is a label for the loop, so the switch can break the loop (and not the switch)
for {
b := int(frame.FrameData[offset])
offset++
switch scanlineType(b) {
case endOfLine:
if y == 0 {
break loop
}
y--
x = 0
case runOfTransparentPixels:
transparentPixels := b & maxRunLength
x += transparentPixels
case runOfOpaquePixels:
for i := 0; i < b; i++ {
indexData[x+y*int(frame.Width)+i] = frame.FrameData[offset]
offset++
}
x += b
}
}
return indexData
}
func scanlineType(b int) scanlineState {
if b == endOfScanLine {
return endOfLine
}
if (b & endOfScanLine) > 0 {
return runOfTransparentPixels
}
return runOfOpaquePixels
}