OpenDiablo2/d2common/d2fileformats/d2dcc/dcc_direction.go

442 lines
13 KiB
Go

package d2dcc
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom"
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
)
const cellsPerRow = 4
// DCCDirection represents a DCCDirection file.
type DCCDirection struct {
OutSizeCoded int
CompressionFlags int
Variable0Bits int
WidthBits int
HeightBits int
XOffsetBits int
YOffsetBits int
OptionalDataBits int
CodedBytesBits int
EqualCellsBitstreamSize int
PixelMaskBitstreamSize int
EncodingTypeBitsreamSize int
RawPixelCodesBitstreamSize int
Frames []*DCCDirectionFrame
PaletteEntries [256]byte
Box d2geom.Rectangle
Cells []*DCCCell
PixelData []byte
HorizontalCellCount int
VerticalCellCount int
PixelBuffer []DCCPixelBufferEntry
}
// CreateDCCDirection creates an instance of a DCCDirection.
func CreateDCCDirection(bm *d2datautils.BitMuncher, file *DCC) *DCCDirection {
var crazyBitTable = []byte{0, 1, 2, 4, 6, 8, 10, 12, 14, 16, 20, 24, 26, 28, 30, 32}
result := &DCCDirection{
OutSizeCoded: int(bm.GetUInt32()),
CompressionFlags: int(bm.GetBits(2)), //nolint:gomnd // binary data
Variable0Bits: int(crazyBitTable[bm.GetBits(4)]), //nolint:gomnd // binary data
WidthBits: int(crazyBitTable[bm.GetBits(4)]), //nolint:gomnd // binary data
HeightBits: int(crazyBitTable[bm.GetBits(4)]), //nolint:gomnd // binary data
XOffsetBits: int(crazyBitTable[bm.GetBits(4)]), //nolint:gomnd // binary data
YOffsetBits: int(crazyBitTable[bm.GetBits(4)]), //nolint:gomnd // binary data
OptionalDataBits: int(crazyBitTable[bm.GetBits(4)]), //nolint:gomnd // binary data
CodedBytesBits: int(crazyBitTable[bm.GetBits(4)]), //nolint:gomnd // binary data
Frames: make([]*DCCDirectionFrame, file.FramesPerDirection),
}
minx := 100000
miny := 100000
maxx := -100000
maxy := -100000
// Load the frame headers
for frameIdx := 0; frameIdx < file.FramesPerDirection; frameIdx++ {
result.Frames[frameIdx] = CreateDCCDirectionFrame(bm, result)
minx = int(d2math.MinInt32(int32(result.Frames[frameIdx].Box.Left), int32(minx)))
miny = int(d2math.MinInt32(int32(result.Frames[frameIdx].Box.Top), int32(miny)))
maxx = int(d2math.MaxInt32(int32(result.Frames[frameIdx].Box.Right()), int32(maxx)))
maxy = int(d2math.MaxInt32(int32(result.Frames[frameIdx].Box.Bottom()), int32(maxy)))
}
result.Box = d2geom.Rectangle{Left: minx, Top: miny, Width: maxx - minx, Height: maxy - miny}
if result.OptionalDataBits > 0 {
log.Panic("Optional bits in DCC data is not currently supported.")
}
if (result.CompressionFlags & 0x2) > 0 {
result.EqualCellsBitstreamSize = int(bm.GetBits(20)) //nolint:gomnd // binary data
}
result.PixelMaskBitstreamSize = int(bm.GetBits(20)) //nolint:gomnd // binary data
if (result.CompressionFlags & 0x1) > 0 {
result.EncodingTypeBitsreamSize = int(bm.GetBits(20)) //nolint:gomnd // binary data
result.RawPixelCodesBitstreamSize = int(bm.GetBits(20)) //nolint:gomnd // binary data
}
for paletteEntryCount, i := 0, 0; i < 256; i++ {
valid := bm.GetBit() != 0
if valid {
result.PaletteEntries[paletteEntryCount] = byte(i)
paletteEntryCount++
}
}
// HERE BE GIANTS:
// Because of the way this thing mashes bits together, BIT offset matters
// here. For example, if you are on byte offset 3, bit offset 6, and
// the EqualCellsBitstreamSize is 20 bytes, then the next bit stream
// will be located at byte 23, bit offset 6!
equalCellsBitstream := d2datautils.CopyBitMuncher(bm)
bm.SkipBits(result.EqualCellsBitstreamSize)
pixelMaskBitstream := d2datautils.CopyBitMuncher(bm)
bm.SkipBits(result.PixelMaskBitstreamSize)
encodingTypeBitsream := d2datautils.CopyBitMuncher(bm)
bm.SkipBits(result.EncodingTypeBitsreamSize)
rawPixelCodesBitstream := d2datautils.CopyBitMuncher(bm)
bm.SkipBits(result.RawPixelCodesBitstreamSize)
pixelCodeandDisplacement := d2datautils.CopyBitMuncher(bm)
// Calculate the cells for the direction
result.calculateCells()
// Calculate the cells for each of the frames
for _, frame := range result.Frames {
frame.recalculateCells(result)
}
// Fill in the pixel buffer
result.fillPixelBuffer(pixelCodeandDisplacement, equalCellsBitstream, pixelMaskBitstream, encodingTypeBitsream, rawPixelCodesBitstream)
// Generate the actual frame pixel data
result.generateFrames(pixelCodeandDisplacement)
result.PixelBuffer = nil
// Verify that everything we expected to read was actually read (sanity check)...
result.verify(equalCellsBitstream, pixelMaskBitstream, encodingTypeBitsream, rawPixelCodesBitstream)
bm.SkipBits(pixelCodeandDisplacement.BitsRead())
return result
}
func (v *DCCDirection) verify(equalCellsBitstream, pixelMaskBitstream, encodingTypeBitsream, rawPixelCodesBitstream *d2datautils.BitMuncher) {
if equalCellsBitstream.BitsRead() != v.EqualCellsBitstreamSize {
log.Panic("Did not read the correct number of bits!")
}
if pixelMaskBitstream.BitsRead() != v.PixelMaskBitstreamSize {
log.Panic("Did not read the correct number of bits!")
}
if encodingTypeBitsream.BitsRead() != v.EncodingTypeBitsreamSize {
log.Panic("Did not read the correct number of bits!")
}
if rawPixelCodesBitstream.BitsRead() != v.RawPixelCodesBitstreamSize {
log.Panic("Did not read the correct number of bits!")
}
}
//nolint:gocognit nolint:gocyclo // Can't reduce
func (v *DCCDirection) generateFrames(pcd *d2datautils.BitMuncher) {
pbIdx := 0
for _, cell := range v.Cells {
cell.LastWidth = -1
cell.LastHeight = -1
}
v.PixelData = make([]byte, v.Box.Width*v.Box.Height)
frameIndex := -1
for _, frame := range v.Frames {
frameIndex++
frame.PixelData = make([]byte, v.Box.Width*v.Box.Height)
c := -1
for _, cell := range frame.Cells {
c++
cellX := cell.XOffset / cellsPerRow
cellY := cell.YOffset / cellsPerRow
cellIndex := cellX + (cellY * v.HorizontalCellCount)
bufferCell := v.Cells[cellIndex]
pbe := v.PixelBuffer[pbIdx]
if (pbe.Frame != frameIndex) || (pbe.FrameCellIndex != c) {
// This buffer cell has an EqualCell bit set to 1, so copy the frame cell or clear it
if (cell.Width != bufferCell.LastWidth) || (cell.Height != bufferCell.LastHeight) {
// Different sizes TODO: Clear the pixels of the frame cell
for y := 0; y < cell.Height; y++ {
for x := 0; x < cell.Width; x++ {
v.PixelData[x+cell.XOffset+((y+cell.YOffset)*v.Box.Width)] = 0
}
}
} else {
// Same sizes
// Copy the old frame cell into the new position
for fy := 0; fy < cell.Height; fy++ {
for fx := 0; fx < cell.Width; fx++ {
// Frame (buff.lastx, buff.lasty) -> Frame (cell.offx, cell.offy)
// Cell (0, 0,) ->
// blit(dir->bmp, dir->bmp, buff_cell->last_x0, buff_cell->last_y0, cell->x0, cell->y0, cell->w, cell->h );
v.PixelData[fx+cell.XOffset+((fy+cell.YOffset)*v.Box.Width)] =
v.PixelData[fx+bufferCell.LastXOffset+((fy+bufferCell.LastYOffset)*v.Box.Width)]
}
}
// Copy it again into the final frame image
for fy := 0; fy < cell.Height; fy++ {
for fx := 0; fx < cell.Width; fx++ {
// blit(cell->bmp, frm_bmp, 0, 0, cell->x0, cell->y0, cell->w, cell->h );
frame.PixelData[fx+cell.XOffset+((fy+cell.YOffset)*v.Box.Width)] = v.PixelData[cell.XOffset+fx+((cell.YOffset+fy)*v.Box.Width)]
}
}
}
} else {
if pbe.Value[0] == pbe.Value[1] {
// Clear the frame
for y := 0; y < cell.Height; y++ {
for x := 0; x < cell.Width; x++ {
v.PixelData[x+cell.XOffset+((y+cell.YOffset)*v.Box.Width)] = pbe.Value[0]
}
}
} else {
// Fill the frame cell with the pixels
bitsToRead := 1
if pbe.Value[1] != pbe.Value[2] {
bitsToRead = 2
}
for y := 0; y < cell.Height; y++ {
for x := 0; x < cell.Width; x++ {
paletteIndex := pcd.GetBits(bitsToRead)
v.PixelData[x+cell.XOffset+((y+cell.YOffset)*v.Box.Width)] = pbe.Value[paletteIndex]
}
}
}
// Copy the frame cell into the frame
for fy := 0; fy < cell.Height; fy++ {
for fx := 0; fx < cell.Width; fx++ {
// blit(cell->bmp, frm_bmp, 0, 0, cell->x0, cell->y0, cell->w, cell->h );
frame.PixelData[fx+cell.XOffset+((fy+cell.YOffset)*v.Box.Width)] = v.PixelData[fx+cell.XOffset+((fy+cell.YOffset)*v.Box.Width)]
}
}
pbIdx++
}
bufferCell.LastWidth = cell.Width
bufferCell.LastHeight = cell.Height
bufferCell.LastXOffset = cell.XOffset
bufferCell.LastYOffset = cell.YOffset
}
// Free up the stuff we no longer need
frame.Cells = nil
}
v.Cells = nil
v.PixelData = nil
v.PixelBuffer = nil
}
//nolint:funlen nolint:gocognit // can't reduce
func (v *DCCDirection) fillPixelBuffer(pcd, ec, pm, et, rp *d2datautils.BitMuncher) {
var pixelMaskLookup = []int{0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4}
lastPixel := uint32(0)
maxCellX := 0
maxCellY := 0
for _, frame := range v.Frames {
if frame == nil {
continue
}
maxCellX += frame.HorizontalCellCount
maxCellY += frame.VerticalCellCount
}
v.PixelBuffer = make([]DCCPixelBufferEntry, maxCellX*maxCellY)
for i := 0; i < maxCellX*maxCellY; i++ {
v.PixelBuffer[i].Frame = -1
v.PixelBuffer[i].FrameCellIndex = -1
}
cellBuffer := make([]*DCCPixelBufferEntry, v.HorizontalCellCount*v.VerticalCellCount)
frameIndex := -1
pbIndex := -1
var pixelMask uint32
for _, frame := range v.Frames {
frameIndex++
originCellX := (frame.Box.Left - v.Box.Left) / cellsPerRow
originCellY := (frame.Box.Top - v.Box.Top) / cellsPerRow
for cellY := 0; cellY < frame.VerticalCellCount; cellY++ {
currentCellY := cellY + originCellY
for cellX := 0; cellX < frame.HorizontalCellCount; cellX++ {
currentCell := originCellX + cellX + (currentCellY * v.HorizontalCellCount)
nextCell := false
tmp := 0
if cellBuffer[currentCell] != nil {
if v.EqualCellsBitstreamSize > 0 {
tmp = int(ec.GetBit())
} else {
tmp = 0
}
if tmp == 0 {
pixelMask = pm.GetBits(4) //nolint:gomnd // binary data
} else {
nextCell = true
}
} else {
pixelMask = 0x0F
}
if nextCell {
continue
}
// Decode the pixels
var pixelStack [4]uint32
lastPixel = 0
numberOfPixelBits := pixelMaskLookup[pixelMask]
encodingType := 0
if (numberOfPixelBits != 0) && (v.EncodingTypeBitsreamSize > 0) {
encodingType = int(et.GetBit())
} else {
encodingType = 0
}
decodedPixel := 0
for i := 0; i < numberOfPixelBits; i++ {
if encodingType != 0 {
pixelStack[i] = rp.GetBits(8) //nolint:gomnd // binary data
} else {
pixelStack[i] = lastPixel
pixelDisplacement := pcd.GetBits(4) //nolint:gomnd // binary data
pixelStack[i] += pixelDisplacement
for pixelDisplacement == 15 {
pixelDisplacement = pcd.GetBits(4) //nolint:gomnd // binary data
pixelStack[i] += pixelDisplacement
}
}
if pixelStack[i] == lastPixel {
pixelStack[i] = 0
break
} else {
lastPixel = pixelStack[i]
decodedPixel++
}
}
oldEntry := cellBuffer[currentCell]
pbIndex++
curIdx := decodedPixel - 1
for i := 0; i < 4; i++ {
if (pixelMask & (1 << uint(i))) != 0 {
if curIdx >= 0 {
v.PixelBuffer[pbIndex].Value[i] = byte(pixelStack[curIdx])
curIdx--
} else {
v.PixelBuffer[pbIndex].Value[i] = 0
}
} else {
v.PixelBuffer[pbIndex].Value[i] = oldEntry.Value[i]
}
}
cellBuffer[currentCell] = &v.PixelBuffer[pbIndex]
v.PixelBuffer[pbIndex].Frame = frameIndex
v.PixelBuffer[pbIndex].FrameCellIndex = cellX + (cellY * frame.HorizontalCellCount)
}
}
}
// Convert the palette entry index into actual palette entries
for i := 0; i <= pbIndex; i++ {
for x := 0; x < 4; x++ {
v.PixelBuffer[i].Value[x] = v.PaletteEntries[v.PixelBuffer[i].Value[x]]
}
}
}
func (v *DCCDirection) calculateCells() {
// Calculate the number of vertical and horizontal cells we need
v.HorizontalCellCount = 1 + (v.Box.Width-1)/cellsPerRow
v.VerticalCellCount = 1 + (v.Box.Height-1)/cellsPerRow
// Calculate the cell widths
cellWidths := make([]int, v.HorizontalCellCount)
if v.HorizontalCellCount == 1 {
cellWidths[0] = v.Box.Width
} else {
for i := 0; i < v.HorizontalCellCount-1; i++ {
cellWidths[i] = 4
}
cellWidths[v.HorizontalCellCount-1] = v.Box.Width - (4 * (v.HorizontalCellCount - 1))
}
// Calculate the cell heights
cellHeights := make([]int, v.VerticalCellCount)
if v.VerticalCellCount == 1 {
cellHeights[0] = v.Box.Height
} else {
for i := 0; i < v.VerticalCellCount-1; i++ {
cellHeights[i] = 4
}
cellHeights[v.VerticalCellCount-1] = v.Box.Height - (4 * (v.VerticalCellCount - 1))
}
// Set the cell widths and heights in the cell buffer
v.Cells = make([]*DCCCell, v.VerticalCellCount*v.HorizontalCellCount)
yOffset := 0
for y := 0; y < v.VerticalCellCount; y++ {
xOffset := 0
for x := 0; x < v.HorizontalCellCount; x++ {
v.Cells[x+(y*v.HorizontalCellCount)] = &DCCCell{
Width: cellWidths[x],
Height: cellHeights[y],
XOffset: xOffset,
YOffset: yOffset,
}
xOffset += 4
}
yOffset += 4
}
}