Home > Programming > Graphic formats > Heretic II mipmap

 

The Heretic II mipmap graphic format is a raster image format introduced by Heretic II game. This graphic format is a MipMap, that means it have the full size picture and some other pictures each one one half the size of the previous picture.File extenstion for this graphic format is m8 (*.m8). Supported bits per pixel for this graphic format is 8bpp. It uses a 256 color palette, just as 8bpp Windows bitmaps.
Header description
Position Size Type Description
0 4 DWORD Magic (hex: 02 00 00 00)
4 32 STRING Name
36 64 INTEGER 16 integer values for each width of picture
100 64 INTEGER 16 integer values for each height of picture
164 64 INTEGER 16 integer values for each offset of picture in file
228 32 STRING Animation name
260 768 RGB 256 color palette
1028 4 DWORD Flags
1032 4 DWORD Contents
1036 4 DWORD Value
Here follows the source code to encapsulate the Heretic II M8 graphic format to a Delphi program. Note that this source code does not provide variable resolutions, just one.

// The Heretic II mipmap graphic format is a raster image format introduced by Heretic II game. 
// This graphic format is a MipMap, that means it have the full size picture and some other 
// pictures each one one half the size of the previous picture.File extenstion for this graphic 
// format is m8 (*.m8). Supported bits per pixel for this graphic format is 8bpp. It uses a 256 
// color palette, just as 8bpp Windows bitmaps.
///////////////////////////////////////////////////
// Author: Jim Valavanis, 
// E-Mail: [email protected]
// Site  : http://www.geocities.com/jimmyvalavanis/
///////////////////////////////////////////////////

unit xM8;

{$P+,S-,W-,R-,T-,X+,H+}
{$C PRELOAD}

interface

uses
  Windows, Forms, SysUtils, Classes, Graphics;

type
  TM8Bitmap = class(TBitmap)
  private
    procedure WriteM8StreamData(Stream: TStream);
    procedure ReadM8StreamData(Stream: TStream);
  protected
    procedure WriteData(Stream: TStream); override;
    procedure ReadData(Stream: TStream); override;
  public
    procedure SaveToStream(Stream: TStream); override;
    procedure LoadFromStream(Stream: TStream); override;
  end;

resourceString
  rsPPMError = 'Error reading M8 file: Wrong file type.';

implementation

{ TM8Bitmap }

type
  TQuake2Palette = packed array[0..255] of packed record R, G, B: byte; end;

  // Heretic2 m8 header
  Miptex_T_m8 = record
    Identifier: integer; // hex: 02 00 00 00
    Name: array[0..31] of char;
    Widths: array[0..15] of Longint;
    Heights: array[0..15] of Longint;
    Offsets: array[0..15] of Longint;
    Animname: array[0..31] of char;
    Palette: TQuake2Palette;
    Flags: Longint;
    Contents: Longint;
    Value: Longint;
  end;

procedure TM8Bitmap.WriteData(Stream: TStream);
begin
  WriteM8StreamData(Stream);
end;

procedure TM8Bitmap.SaveToStream(Stream: TStream);
begin
  WriteM8StreamData(Stream);
end;

procedure TM8Bitmap.LoadFromStream(Stream: TStream);
begin
  ReadM8StreamData(Stream);
end;

procedure TM8Bitmap.ReadData(Stream: TStream);
begin
  ReadM8StreamData(Stream);
end;

procedure TM8Bitmap.ReadM8StreamData(Stream: TStream);
var
  aBitmap: TBitmap;
  Header: Miptex_T_m8;
  i, j: integer;
  P1: PByteArray;
  lpal: PLogPalette;
  hpal: HPALETTE;
  nearBlack: integer;
  pos: integer;
begin
  pos := Stream.Position;
  Stream.Read(Header, SizeOf(Header));
  aBitmap := TBitmap.Create;
  GetMem(lpal, SizeOf(TLogPalette) + SizeOf(TPaletteEntry) * 255);
  hpal := 0;
  try
    aBitmap.Width := Header.Widths[0];
    aBitmap.Height := Header.Heights[0];
    aBitmap.PixelFormat := pf8Bit;

    lpal.palVersion := $300;
    lpal.palNumEntries := 256;
    lpal.palPalEntry[0].peRed := 0;
    lpal.palPalEntry[0].peGreen := 0;
    lpal.palPalEntry[0].peBlue := 0;
    for j := 1 to 255 do
    begin
      lpal.palPalEntry[j].peRed := Header.Palette[j].r;
      lpal.palPalEntry[j].peGreen := Header.Palette[j].g;
      lpal.palPalEntry[j].peBlue := Header.Palette[j].b;
    end;
    nearBlack := 1;

    for j := 255 downto 1 do
    begin
      if ((lpal.palPalEntry[j].peRed +
           lpal.palPalEntry[j].peGreen +
           lpal.palPalEntry[j].peBlue) <
          (lpal.palPalEntry[nearBlack].peRed +
           lpal.palPalEntry[nearBlack].peGreen +
           lpal.palPalEntry[nearBlack].peBlue)) and
         ((lpal.palPalEntry[j].peRed +
           lpal.palPalEntry[j].peGreen +
           lpal.palPalEntry[j].peBlue) <> 0) then nearBlack := j;
    end;

    for j := 255 downto 1 do
    begin
      if lpal.palPalEntry[j].peRed + lpal.palPalEntry[j].peGreen + lpal.palPalEntry[j].peBlue = 0 then
        lpal.palPalEntry[j] := lpal.palPalEntry[nearBlack];
    end;

    hpal := CreatePalette(lpal^);

    if hpal <> 0 then
      aBitmap.Palette := hpal;

    stream.Position := pos + Header.Offsets[0];
    for i := 0 to aBitmap.Height - 1 do
    begin
      P1 := aBitmap.Scanline[i];
      Stream.Read(P1^, aBitmap.width);
      for j := 0 to aBitmap.Width -  1 do
        if P1[j] = 0 then P1[j] := nearBlack;
    end;
    Assign(aBitmap);
  finally
    aBitmap.Free;
    FreeMem(lpal, SizeOf(TLogPalette) + SizeOf(TPaletteEntry) * 255);
    if hPal <> 0 then
      DeleteObject(hPal);
  end;
end;

type
  TColorAppearence = record
    color: TColor;
    num: integer;
  end;

  TColorAppearences = array[0..$FFFF] of TColorAppearence;
  PColorAppearences = ^TColorAppearences;

function AlmostEqualColors(c1, c2: TColor): boolean;
begin
  result := sqr(integer(GetRValue(c1)) - integer(GetRValue(c2))) +
            sqr(integer(GetGValue(c1)) - integer(GetGValue(c2))) +
            sqr(integer(GetBValue(c1)) - integer(GetBValue(c2))) <= 256;
end;

procedure ForceBitmapTo8bpp(bmp: TBitmap);
// Converts a bitmap to 8 bits per pixel, returns false
// if bitmap has more than 256 unique colors
var CC: PColorAppearences;
    i, j, k: integer;
    b: PByteArray;
    numC: integer;
    c: TColor;
    found: boolean;
    lpal: PLogPalette;
    hpal: HPALETTE;
    newBMPData: PByteArray;
    dist, dist1: integer;
    index: integer;
begin
  if bmp.PixelFormat in [pf1bit, pf4bit] then
    bmp.PixelFormat := pf8bit
  else if bmp.PixelFormat <> pf8bit then
  begin
    bmp.PixelFormat := pf24bit;
    numC := 16;
    CC := nil;
    ReAllocMem(CC, SizeOf(TColorAppearence));
    // Default Windows Palette
    CC[0].color := RGB(0, 0, 0);
    CC[0].num := 0;

    GetMem(newBMPData, bmp.Width * bmp.Height);
    for i := 0 to bmp.Height - 1 do
    begin
      b := bmp.ScanLine[i];
      for j := 0 to bmp.Width - 1 do
      begin
        c := RGB(b[3 * j + 2], b[3 * j + 1], b[3 * j]);
        found := false;
        for k := 0 to numC - 1 do
        begin
        // Color already exists in the palette, increase appearences counter
          if AlmostEqualColors(CC[k].color, c) then
          begin
            found := true;
            CC[k].num := CC[k].num + 1;
            newBMPData[i * bmp.Width + j] := k;
            break;
          end;
        end;
        // New color, add it to the palette
        if not found then
        begin
          if numC < 256 then  // Less than 256 colors
          begin
            inc(numC);
            ReAllocMem(CC, numC * SizeOf(TColorAppearence));
            newBMPData[i * bmp.Width + j] := numC - 1;
            CC[numC - 1].color := c;
            CC[numC - 1].num := 1;
          end
          else
          begin
          // Find the closest color
            dist := MAXINT;
            index := 0;
            for k := 0 to 255 do
            begin
              dist1 := abs(integer(GetRValue(CC[k].color)) - integer(GetRValue(c))) *
                       abs(integer(GetGValue(CC[k].color)) - integer(GetGValue(c))) *
                       abs(integer(GetBValue(CC[k].color)) - integer(GetBValue(c)));
              if dist1 < dist then
              begin
                dist := dist1;
                index := k;
              end;
            end;
            newBMPData[i * bmp.Width + j] := index;
          end;
        end;
      end;
    end;
    GetMem(lpal, SizeOf(TLogPalette) + SizeOf(TPaletteEntry) * 255);
    lpal.palVersion := $300;
    lpal.palNumEntries := 256;
    for i := 0 to numC - 1 do
    begin
      lpal.palPalEntry[i].peRed := GetRValue(CC[i].color);
      lpal.palPalEntry[i].peGreen := GetGValue(CC[i].color);
      lpal.palPalEntry[i].peBlue := GetBValue(CC[i].color);
    end;
    for i := numC to 255 do
    begin
      lpal.palPalEntry[i].peRed := 0;
      lpal.palPalEntry[i].peGreen := 0;
      lpal.palPalEntry[i].peBlue := 0;
    end;
    bmp.PixelFormat := pf8bit;
    hpal := CreatePalette(lpal^);
    if hpal <> 0 then bmp.Palette := hpal;
    for i := 0 to bmp.Height - 1 do
    begin
      b := bmp.ScanLine[i];
      for j := 0 to bmp.Width - 1 do
        b[j] := newBMPData[i * bmp.Width + j];
    end;
    FreeMem(lpal, SizeOf(TLogPalette) + SizeOf(TPaletteEntry) * 255);

    FreeMem(newBMPData, bmp.Width * bmp.Height);
    ReAllocMem(CC, 0);
  end;
end;

procedure TM8Bitmap.WriteM8StreamData(Stream: TStream);
var
  aBitmap: TBitmap;
  i: integer;
  P1: PByteArray;
  Header: Miptex_T_m8;
  Entries: array[0..255] of TPaletteEntry;
begin
  aBitmap := TBitmap.Create;
  try
    aBitmap.Assign(self);
    if aBitmap.PixelFormat <> pf8bit then
      ForceBitmapTo8bpp(aBitmap);

    FillChar(Header, SizeOf(Header), Chr(0));
    GetPaletteEntries(aBitmap.Palette, 0, 255, Entries);
    for i := 0 to 255 do
    begin
      Header.Palette[i].R := Entries[i].peRed;
      Header.Palette[i].G := Entries[i].peGreen;
      Header.Palette[i].B := Entries[i].peBlue;
    end;
    Header.Identifier := 2; // Default Heretic2 id

    for i := 0 to 15 do
    begin
      Header.Widths[i] := aBitmap.Width;
      Header.Heights[i] := aBitmap.Height;
      Header.Offsets[i] := SizeOf(Header);
    end;
    Stream.Write(Header, SizeOf(Header));
    for i := 0 to aBitmap.Height - 1 do
    begin
      P1 := aBitmap.ScanLine[i];
      Stream.Write(P1^, aBitmap.Width);
    end;
  finally
    aBitmap.Free;
  end;
end;

initialization
  { Register the TM8Bitmap as a new graphic file format
    now all the TPicture storage stuff can access our new
    M8 graphic format !
  }
  TPicture.RegisterFileFormat('M8','M8 (Heretic 2) Mipmap bitmap', TM8Bitmap);

finalization
  TPicture.UnregisterGraphicClass(TM8Bitmap);

end.

© 2004 Jim Valavanis

download (3Kb)

1