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 custom pitch when writing bitmaps #256

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2264fa4
Include OP2BmpLoader in external OP2Utility.h
Brett208 Feb 10, 2019
ffa5afb
Allow OP2BmpLoader to pass image, layer, frame, and animation counts
Brett208 Feb 10, 2019
98b7cab
Add OP2BmpLoader constructor that can load data from an artFile stream
Brett208 Feb 10, 2019
c9d5edd
Allow OP2BmpLoader to pass a 1 or 8 bit palette as appropriate
Brett208 Feb 10, 2019
82e89cf
Mark ExtractImage internal variables as const where appropriate
Brett208 Feb 10, 2019
dc5b645
Improve and standardize casing on function description comments
Brett208 Feb 10, 2019
bb81732
Rename CalculatePitch to CalculateDefaultPitch
Brett208 Feb 10, 2019
3942cea
Allow setting a custom pitch when writing bitmaps
Brett208 Feb 10, 2019
d2546c1
Update unit tests to include pitch when writing a bitmap
Brett208 Feb 10, 2019
a3a853c
Update function name in unit test to match when calculating default p…
Brett208 Feb 10, 2019
967ce2d
Move FindPitch to a public Bmp header and add a non-static version of…
Brett208 Feb 10, 2019
413b1b6
Fix bug in VerifyPixelSizeMatchesImageDimensionsWithPitch and update …
Brett208 Feb 10, 2019
70f4280
Update comments to standardize wording to 4 bytes when mentioning def…
Brett208 Feb 10, 2019
1869af6
Add a default monochrome bitmap header
Brett208 Feb 10, 2019
96e2d11
Update comments
DanRStevens Feb 11, 2019
df91957
Remove extra qualification on member function
DanRStevens Feb 11, 2019
b3f7803
Merge branch 'master' into Allow-Custom-Pitch-When-Writing-Bitmaps
Brett208 Feb 11, 2019
c3323f6
Add missing class name to static property definition
Brett208 Feb 12, 2019
893ff39
change type name from Palette to Palette8Bit
Brett208 Feb 12, 2019
04521ba
Fix bug where Reader was using width instead of pitch
Brett208 Feb 12, 2019
c48a75d
Merge branch 'Allow-Custom-Pitch-When-Writing-Bitmaps' of https://git…
Brett208 Feb 12, 2019
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
3 changes: 2 additions & 1 deletion include/OP2Utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@

#include "../src/Map/Map.h"

#include "../src/Sprite/ArtFile.h"
#include "../src/Bitmap/BitmapFile.h"
#include "../src/Sprite/ArtFile.h"
#include "../src/Sprite/OP2BmpLoader.h"

#include "../src/Stream/FileReader.h"
#include "../src/Stream/SliceReader.h"
Expand Down
28 changes: 24 additions & 4 deletions src/Bitmap/BitmapFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ BitmapFile BitmapFile::CreateDefaultIndexed(uint16_t bitCount, uint32_t width, u
BitmapFile bitmapFile;
bitmapFile.imageHeader = ImageHeader::Create(width, height, bitCount);
bitmapFile.palette.resize(bitmapFile.imageHeader.CalcMaxIndexedPaletteSize());
bitmapFile.pixels.resize(bitmapFile.imageHeader.CalculatePitch() * height);
bitmapFile.pixels.resize(bitmapFile.imageHeader.CalculateDefaultPitch() * height);

const std::size_t pixelOffset = sizeof(BmpHeader) + sizeof(ImageHeader) + bitmapFile.palette.size() * sizeof(Color);
const std::size_t bitmapFileSize = pixelOffset + bitmapFile.pixels.size() * sizeof(uint8_t);
Expand Down Expand Up @@ -35,16 +35,36 @@ void BitmapFile::VerifyIndexedPaletteSizeDoesNotExceedBitCount(uint16_t bitCount

void BitmapFile::VerifyPixelSizeMatchesImageDimensionsWithPitch() const
{
BitmapFile::VerifyPixelSizeMatchesImageDimensionsWithPitch(imageHeader.bitCount, imageHeader.width, imageHeader.height, pixels.size());
VerifyPixelSizeMatchesImageDimensionsWithPitch(imageHeader.bitCount, FindPitch(), imageHeader.height, pixels.size());
}

void BitmapFile::VerifyPixelSizeMatchesImageDimensionsWithPitch(uint16_t bitCount, int32_t width, int32_t height, std::size_t pixelsWithPitchSize)
void BitmapFile::VerifyPixelSizeMatchesImageDimensionsWithPitch(uint16_t bitCount, std::size_t pitch, int32_t height, std::size_t pixelsWithPitchSize)
{
if (pixelsWithPitchSize != ImageHeader::CalculatePitch(bitCount, width) * std::abs(height)) {
if (pixelsWithPitchSize != pitch * std::abs(height)) {
throw std::runtime_error("The size of pixels does not match the image's height time pitch");
}
}

std::size_t BitmapFile::FindPitch() const
{
return FindPitch(imageHeader.width, imageHeader.height, pixels.size());
}

std::size_t BitmapFile::FindPitch(std::size_t width, std::size_t height, std::size_t pixelCount)
{
if (pixelCount % height != 0) {
throw std::runtime_error("Unable to calculate a valid pitch based on height and pixel count");
}

const std::size_t pitch = pixelCount / height;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hack of mine is causing the unit tests to fail. They happen to be fairly robust for this small section of the code compared to others.

The next step is probably to figure out a proper bit twiddle or calculation that determines pitch without assuming it is a 4 byte interval. @DanRStevens, feel free to chime in if you have something up your sleeve.

-Brett

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pitch is either assumed, or given. It's not generally calculated (other than in an assumption, such as rounding the scanline byte width up to a multiple of 4 bytes). The problem with trying to calculate it, is the data source could always have chosen a larger pitch than is necessary.

I find it a little non-intuitive to use pixelCount here. When I think pixel could, I generally assume it's simply width * height, which is not particularly useful for calculating pitch if you already have the width. Plus, pitch is a byte measurement, not a pixel measurement. The bitDepth needs to be accounted for.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are wanting to work on this branch feel free. I'm not going to pick it up again for a long time.

-Brett

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I quite have the context to pick this one up right now.


if (pitch < width) {
throw std::runtime_error("Calculated pitch would be smaller than image pixel width");
}

return pitch;
}

void BitmapFile::VerifyIndexedImageForSerialization(uint16_t bitCount)
{
if (!ImageHeader::IsIndexedImage(bitCount)) {
Expand Down
27 changes: 15 additions & 12 deletions src/Bitmap/BitmapFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,31 @@ class BitmapFile
BmpHeader bmpHeader;
ImageHeader imageHeader;
std::vector<Color> palette;
std::vector<uint8_t> pixels;
std::vector<uint8_t> pixels; // Includes pitch

// Uses a default 4 byte pitch
static BitmapFile CreateDefaultIndexed(uint16_t bitCount, uint32_t width, uint32_t height);

// BMP Reader only supports Indexed Color palettes (1, 2, and 8 bit BMPs).
// BMP Reader only supports indexed color palettes (1, 2, and 8 bit BMPs).
static BitmapFile ReadIndexed(const std::string& filename);
static BitmapFile ReadIndexed(Stream::BidirectionalSeekableReader& seekableReader);

// BMP Writer only supporting Indexed Color palettes (1, 2, and 8 bit BMPs).
// @indexedPixels: Must include padding to fill each image row out to the next 4 byte memory border (pitch).
static void WriteIndexed(std::string filename, uint16_t bitCount, int32_t width, int32_t height, std::vector<Color> palette, const std::vector<uint8_t>& indexedPixels);
static void WriteIndexed(Stream::BidirectionalSeekableWriter& seekableWriter, uint16_t bitCount, int32_t width, int32_t height, std::vector<Color> palette, const std::vector<uint8_t>& indexedPixels);
// BMP Writer only supports indexed color palettes (1, 2, and 8 bit BMPs).
// @indexedPixels: Size must be padded to a multiple of pitch. (4 byte pitch is typical)
static void WriteIndexed(std::string filename, uint16_t bitCount, int32_t width, int32_t height, std::size_t pitch, std::vector<Color> palette, const std::vector<uint8_t>& indexedPixels);
static void WriteIndexed(Stream::BidirectionalSeekableWriter& seekableWriter, uint16_t bitCount, int32_t width, int32_t height, std::size_t pitch, std::vector<Color> palette, const std::vector<uint8_t>& indexedPixels);
static void WriteIndexed(std::string filename, const BitmapFile& bitmapFile);

void VerifyIndexedPaletteSizeDoesNotExceedBitCount() const;
static void VerifyIndexedPaletteSizeDoesNotExceedBitCount(uint16_t bitCount, std::size_t paletteSize);

// Check the pixel count is correct and already includes dummy pixels out to next 4 byte boundary.
// @width: Width in pixels. Do not include the pitch in width.
// @pixelsWithPitchSize: Number of pixels including padding pixels to next 4 byte boundary.
// Check the pixel count is correct and already includes dummy pixels out to pitch boundary.
void VerifyPixelSizeMatchesImageDimensionsWithPitch() const;
static void VerifyPixelSizeMatchesImageDimensionsWithPitch(uint16_t bitCount, int32_t width, int32_t height, std::size_t pixelsWithPitchSize);
// @pixelsWithPitchSize: Number of pixels including padding pixels to next pitch boundary.
static void VerifyPixelSizeMatchesImageDimensionsWithPitch(uint16_t bitCount, std::size_t pitch, int32_t height, std::size_t pixelsWithPitchSize);

std::size_t FindPitch() const;
static std::size_t FindPitch(std::size_t width, std::size_t height, std::size_t pixelCount);

void Validate() const;

Expand All @@ -55,8 +58,8 @@ class BitmapFile
static void ReadPixels(Stream::BidirectionalSeekableReader& seekableReader, BitmapFile& bitmapFile);

// Write
static void WriteHeaders(Stream::BidirectionalSeekableWriter& seekableWriter, uint16_t bitCount, int width, int height, const std::vector<Color>& palette);
static void WritePixels(Stream::BidirectionalSeekableWriter& seekableWriter, const std::vector<uint8_t>& pixels, int32_t width, uint16_t bitCount);
static void WriteHeaders(Stream::BidirectionalSeekableWriter& seekableWriter, uint16_t bitCount, int width, int height, std::size_t pitch, const std::vector<Color>& palette);
static void WritePixels(Stream::BidirectionalSeekableWriter& seekableWriter, const std::vector<uint8_t>& pixels, int32_t width, uint16_t bitCount, std::size_t pitch);
};

bool operator==(const BitmapFile& lhs, const BitmapFile& rhs);
Expand Down
2 changes: 1 addition & 1 deletion src/Bitmap/Color.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ static_assert(4 == sizeof(Color), "Color is an unexpected size");
bool operator==(const Color& lhs, const Color& rhs);
bool operator!=(const Color& lhs, const Color& rhs);

using Palette = std::array<Color, 256>;
using Palette8Bit = std::array<Color, 256>;

namespace DiscreteColor
{
Expand Down
6 changes: 3 additions & 3 deletions src/Bitmap/ImageHeader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ void ImageHeader::VerifyValidBitCount(uint16_t bitCount)
}
}

std::size_t ImageHeader::CalculatePitch() const
std::size_t ImageHeader::CalculateDefaultPitch() const
{
return ImageHeader::CalculatePitch(bitCount, width);
return ImageHeader::CalculateDefaultPitch(bitCount, width);
}

std::size_t ImageHeader::CalculatePitch(uint16_t bitCount, int32_t width)
std::size_t ImageHeader::CalculateDefaultPitch(uint16_t bitCount, int32_t width)
{
const auto bytesOfPixelsPerRow = CalcPixelByteWidth(bitCount, width);
return (bytesOfPixelsPerRow + 3) & ~3;
Expand Down
5 changes: 3 additions & 2 deletions src/Bitmap/ImageHeader.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ struct ImageHeader
void VerifyValidBitCount() const;
static void VerifyValidBitCount(uint16_t bitCount);

std::size_t CalculatePitch() const;
static std::size_t CalculatePitch(uint16_t bitCount, int32_t width);
// Default pitch granularity is 4 bytes
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had intentionally left this as interval. Perhaps it is a regional language different but interval seems a better and easier to understand choice than granularity to me. Being a comment, it probably isn't worth arguing over now that it has been changed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A disagreement in terminology might signal a disagreement about what is intended here.

I would define an interval as having a start and end point. Granularity is about the minimum spacing of values.

Here the pitch is rounded to some multiple of a certain value. That is defining the granularity of the value. As in, values are a multiple of 4 bytes. Is does not define the length of an interval as 4 bytes. For example, the spacing between scanlines could easily be 12 bytes, even though the granularity of that value is 4 bytes.

More deeply, I'm not sure I would really consider pitch to be an interval. It's more of a spacing between scanlines. If pitch were an interval, I'd be uncertain how to define the start and end points.

Looking back at std::size_t CalculateDefaultPitch() const;, I'm actually a little uncertain what the comment is trying to tell me, or why it is important. Is it trying to tell me the value is a multiple of 4 bytes? From the example test code, that would seem to be the case.

I vaguely recall some requirement that pitch is a minimum of 4 bytes, though can be a larger multiple of it.

I might also consider the phrasing:
Default pitch is the scanline byte width rounded up to a multiple of 4 bytes

std::size_t CalculateDefaultPitch() const;
static std::size_t CalculateDefaultPitch(uint16_t bitCount, int32_t width);

// Does not include padding
std::size_t CalcPixelByteWidth() const;
Expand Down
5 changes: 3 additions & 2 deletions src/Bitmap/IndexedBmpReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ void BitmapFile::ReadPalette(Stream::BidirectionalSeekableReader& seekableReader

void BitmapFile::ReadPixels(Stream::BidirectionalSeekableReader& seekableReader, BitmapFile& bitmapFile)
{
std::size_t pixelContainerSize = bitmapFile.bmpHeader.size - bitmapFile.bmpHeader.pixelOffset;
BitmapFile::VerifyPixelSizeMatchesImageDimensionsWithPitch(bitmapFile.imageHeader.bitCount, bitmapFile.imageHeader.width, bitmapFile.imageHeader.height, pixelContainerSize);
const std::size_t pixelContainerSize = bitmapFile.bmpHeader.size - bitmapFile.bmpHeader.pixelOffset;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might be more of a maximum upper bound, rather than an exact size. There may be other sections stored in the bitmap file, after the pixel data, before the end of the file.

const std::size_t pitch = FindPitch(bitmapFile.imageHeader.width, bitmapFile.imageHeader.height, pixelContainerSize);
BitmapFile::VerifyPixelSizeMatchesImageDimensionsWithPitch(bitmapFile.imageHeader.bitCount, pitch, bitmapFile.imageHeader.height, pixelContainerSize);

bitmapFile.pixels.clear();
bitmapFile.pixels.resize(pixelContainerSize);
Expand Down
22 changes: 11 additions & 11 deletions src/Bitmap/IndexedBmpWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,35 @@ void BitmapFile::WriteIndexed(std::string filename, const BitmapFile& bitmapFile
}

bitmapFile.Validate();
const auto pitch = FindPitch(bitmapFile.imageHeader.width, bitmapFile.imageHeader.height, bitmapFile.pixels.size());

WriteIndexed(filename, bitmapFile.imageHeader.bitCount, bitmapFile.imageHeader.width, bitmapFile.imageHeader.height, bitmapFile.palette, bitmapFile.pixels);
WriteIndexed(filename, bitmapFile.imageHeader.bitCount, bitmapFile.imageHeader.width, bitmapFile.imageHeader.height, pitch, bitmapFile.palette, bitmapFile.pixels);
}

void BitmapFile::WriteIndexed(std::string filename, uint16_t bitCount, int32_t width, int32_t height, std::vector<Color> palette, const std::vector<uint8_t>& indexedPixels)
void BitmapFile::WriteIndexed(std::string filename, uint16_t bitCount, int32_t width, int32_t height, std::size_t pitch, std::vector<Color> palette, const std::vector<uint8_t>& indexedPixels)
{
Stream::FileWriter fileWriter(filename);
WriteIndexed(fileWriter, bitCount, width, height, palette, indexedPixels);
WriteIndexed(fileWriter, bitCount, width, height, pitch, palette, indexedPixels);
}

void BitmapFile::WriteIndexed(Stream::BidirectionalSeekableWriter& seekableWriter, uint16_t bitCount, int32_t width, int32_t height, std::vector<Color> palette, const std::vector<uint8_t>& indexedPixels)
void BitmapFile::WriteIndexed(Stream::BidirectionalSeekableWriter& seekableWriter, uint16_t bitCount, int32_t width, int32_t height, std::size_t pitch, std::vector<Color> palette, const std::vector<uint8_t>& indexedPixels)
{
VerifyIndexedImageForSerialization(bitCount);
VerifyIndexedPaletteSizeDoesNotExceedBitCount(bitCount, palette.size());
VerifyPixelSizeMatchesImageDimensionsWithPitch(bitCount, width, height, indexedPixels.size());
VerifyPixelSizeMatchesImageDimensionsWithPitch(bitCount, pitch, height, indexedPixels.size());

palette.resize(ImageHeader::CalcMaxIndexedPaletteSize(bitCount), DiscreteColor::Black);

WriteHeaders(seekableWriter, bitCount, width, height, palette);
WriteHeaders(seekableWriter, bitCount, width, height, pitch, palette);
seekableWriter.Write(palette);

WritePixels(seekableWriter, indexedPixels, width, bitCount);
WritePixels(seekableWriter, indexedPixels, width, bitCount, pitch);
}

void BitmapFile::WriteHeaders(Stream::BidirectionalSeekableWriter& seekableWriter, uint16_t bitCount, int width, int height, const std::vector<Color>& palette)
void BitmapFile::WriteHeaders(Stream::BidirectionalSeekableWriter& seekableWriter, uint16_t bitCount, int width, int height, std::size_t pitch, const std::vector<Color>& palette)
{
std::size_t pixelOffset = sizeof(BmpHeader) + sizeof(ImageHeader) + palette.size() * sizeof(Color);
std::size_t fileSize = pixelOffset + ImageHeader::CalculatePitch(bitCount, width) * std::abs(height);
std::size_t fileSize = pixelOffset + pitch * std::abs(height);

if (fileSize > UINT32_MAX) {
throw std::runtime_error("Bitmap size is too large to save to disk.");
Expand All @@ -51,9 +52,8 @@ void BitmapFile::WriteHeaders(Stream::BidirectionalSeekableWriter& seekableWrite
seekableWriter.Write(imageHeader);
}

void BitmapFile::WritePixels(Stream::BidirectionalSeekableWriter& seekableWriter, const std::vector<uint8_t>& pixels, int32_t width, uint16_t bitCount)
void BitmapFile::WritePixels(Stream::BidirectionalSeekableWriter& seekableWriter, const std::vector<uint8_t>& pixels, int32_t width, uint16_t bitCount, std::size_t pitch)
{
const auto pitch = ImageHeader::CalculatePitch(bitCount, width);
const auto bytesOfPixelsPerRow = ImageHeader::CalcPixelByteWidth(bitCount, width);
const std::vector<uint8_t> padding(pitch - bytesOfPixelsPerRow, 0);

Expand Down
2 changes: 1 addition & 1 deletion src/Sprite/ArtFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Stream {
struct ArtFile
{
public:
std::vector<Palette> palettes;
std::vector<Palette8Bit> palettes;
std::vector<ImageMeta> imageMetas;
std::vector<Animation> animations;
uint32_t unknownAnimationCount;
Expand Down
2 changes: 1 addition & 1 deletion src/Sprite/ImageMeta.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct ImageMeta {

static_assert(2 == sizeof(ImageType), "ImageMeta::ImageType is an unexpected size");

uint32_t scanLineByteWidth; //number of bytes in each scan line of image (this should be the width of the image rounded up to a 32 bit boundary)
uint32_t scanLineByteWidth; //number of bytes in each scan line of image (this should be the width of the image rounded up to a 4 byte boundary)
uint32_t pixelDataOffset; // Offset of the pixel data in the .bmp file
uint32_t height; // Height of image in pixels
uint32_t width; // Width of image in pixels
Expand Down
39 changes: 33 additions & 6 deletions src/Sprite/OP2BmpLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,25 @@
#include <algorithm>
#include <limits>

const std::vector<Color> OP2BmpLoader::DefaultMonochromePalette{ Color{0, 0, 0}, Color{255, 255, 255} };

OP2BmpLoader::OP2BmpLoader(std::string bmpFilename, std::string artFilename) :
bmpReader(bmpFilename), artFile(ArtFile::Read(artFilename)) { }

OP2BmpLoader::OP2BmpLoader(std::string bmpFilename, Stream::BidirectionalSeekableReader& artFileStream) :
bmpReader(bmpFilename), artFile(ArtFile::Read(artFileStream)) { }

void OP2BmpLoader::ExtractImage(std::size_t index, const std::string& filenameOut)
{
artFile.VerifyImageIndexInBounds(index);

ImageMeta& imageMeta = artFile.imageMetas[index];
const ImageMeta& imageMeta = artFile.imageMetas[index];

std::vector<Color> palette(artFile.palettes[imageMeta.paletteIndex].size());
std::copy(artFile.palettes[imageMeta.paletteIndex].begin(), artFile.palettes[imageMeta.paletteIndex].end(), palette.begin());
const auto palette = CreatePalette(imageMeta);

std::size_t pixelOffset = imageMeta.pixelDataOffset + 14 + sizeof(ImageHeader) + palette.size() * sizeof(Color);
const std::size_t pixelOffset = imageMeta.pixelDataOffset + 14 + sizeof(ImageHeader) + palette.size() * sizeof(Color);

auto pixels = GetPixels(pixelOffset, imageMeta.scanLineByteWidth * imageMeta.height);
const auto pixels = GetPixels(pixelOffset, imageMeta.scanLineByteWidth * imageMeta.height);

std::vector<uint8_t> pixelContainer(imageMeta.scanLineByteWidth * imageMeta.height);
(*pixels).Read(pixelContainer);
Expand All @@ -29,7 +33,30 @@ void OP2BmpLoader::ExtractImage(std::size_t index, const std::string& filenameOu
throw std::runtime_error("Image height is too large to fit in standard bitmap file format.");
}

BitmapFile::WriteIndexed(filenameOut, imageMeta.GetBitCount(), imageMeta.width, -static_cast<int32_t>(imageMeta.height), palette, pixelContainer);
BitmapFile::WriteIndexed(filenameOut, imageMeta.GetBitCount(), imageMeta.width, -static_cast<int32_t>(imageMeta.height), imageMeta.scanLineByteWidth, palette, pixelContainer);
}

std::size_t OP2BmpLoader::FrameCount(std::size_t animationIndex) const {
return artFile.animations[animationIndex].frames.size();
}

std::size_t OP2BmpLoader::LayerCount(std::size_t animationIndex, std::size_t frameIndex) const {
return artFile.animations[animationIndex].frames[frameIndex].layers.size();
}

std::vector<Color> OP2BmpLoader::CreatePalette(const ImageMeta& imageMeta) const
{
std::vector<Color> palette;

if (imageMeta.GetBitCount() == 1) {
return DefaultMonochromePalette;
}
else {
palette.resize(artFile.palettes[imageMeta.paletteIndex].size());
std::copy(artFile.palettes[imageMeta.paletteIndex].begin(), artFile.palettes[imageMeta.paletteIndex].end(), palette.begin());
DanRStevens marked this conversation as resolved.
Show resolved Hide resolved
}

return palette;
}

std::unique_ptr<Stream::FileSliceReader> OP2BmpLoader::GetPixels(std::size_t startingIndex, std::size_t length)
Expand Down
8 changes: 8 additions & 0 deletions src/Sprite/OP2BmpLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,23 @@ class OP2BmpLoader
{
public:
OP2BmpLoader(std::string bmpFilename, std::string artFilename);
OP2BmpLoader(std::string bmpFilename, Stream::BidirectionalSeekableReader& artFileStream);

void ExtractImage(std::size_t index, const std::string& filenameOut);

inline std::size_t ImageCount() const { return artFile.imageMetas.size(); }
inline std::size_t AnimationCount() const { return artFile.animations.size(); }
std::size_t FrameCount(std::size_t animationIndex) const;
std::size_t LayerCount(std::size_t animationIndex, std::size_t frameIndex) const;

private:
// Bmp loader for Outpost 2 specific BMP file
// Contains many images in pixels section with a default palette.
// Actual palette data and range of pixels to form each image is contained in the .prt file.
Stream::FileReader bmpReader;
ArtFile artFile;
static const std::vector<Color> DefaultMonochromePalette;

std::vector<Color> CreatePalette(const ImageMeta& imageMeta) const;
std::unique_ptr<Stream::FileSliceReader> GetPixels(std::size_t startingIndex, std::size_t length);
};
2 changes: 1 addition & 1 deletion src/Sprite/PaletteHeader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ PaletteHeader::PaletteHeader() : remainingTagCount(0) {}

PaletteHeader::PaletteHeader(const ArtFile& artFile) : remainingTagCount(1)
{
uint64_t dataSize = sizeof(Palette);
uint64_t dataSize = sizeof(Palette8Bit);

uint64_t overallSize = 4 + sizeof(overallHeader) + sizeof(sectionHeader) + sizeof(remainingTagCount) + dataSize;

Expand Down
8 changes: 4 additions & 4 deletions test/Bitmap/BitmapFile.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ TEST(BitmapFile, VerifyIndexedPaletteSizeDoesNotExceedBitCount)

TEST(BitmapFile, VerifyPixelSizeMatchesImageDimensionsWithPitch)
{
EXPECT_NO_THROW(BitmapFile::VerifyPixelSizeMatchesImageDimensionsWithPitch(1, 1, 1, 4));
EXPECT_THROW(BitmapFile::VerifyPixelSizeMatchesImageDimensionsWithPitch(1, 1, 1, 1), std::runtime_error);
EXPECT_NO_THROW(BitmapFile::VerifyPixelSizeMatchesImageDimensionsWithPitch(1, 4, 1, 4));
EXPECT_THROW(BitmapFile::VerifyPixelSizeMatchesImageDimensionsWithPitch(1, 4, 1, 1), std::runtime_error);

// Test non-static version of function
BitmapFile bitmapFile = BitmapFile::CreateDefaultIndexed(1, 1, 1);;
BitmapFile bitmapFile = BitmapFile::CreateDefaultIndexed(1, 1, 2);;
EXPECT_NO_THROW(bitmapFile.VerifyPixelSizeMatchesImageDimensionsWithPitch());
bitmapFile.pixels.resize(1, 0);
bitmapFile.pixels.resize(3, 0); // Cannot calculate a valid pitch
EXPECT_THROW(bitmapFile.VerifyPixelSizeMatchesImageDimensionsWithPitch(), std::runtime_error);
}

Expand Down
20 changes: 10 additions & 10 deletions test/Bitmap/ImageHeader.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,23 +77,23 @@ TEST(ImageHeader, VerifyValidBitCount)

TEST(ImageHeader, CalculatePitch)
{
EXPECT_EQ(4, ImageHeader::CalculatePitch(1, 1));
EXPECT_EQ(4, ImageHeader::CalculatePitch(1, 32));
EXPECT_EQ(8, ImageHeader::CalculatePitch(1, 33));
EXPECT_EQ(4, ImageHeader::CalculateDefaultPitch(1, 1));
EXPECT_EQ(4, ImageHeader::CalculateDefaultPitch(1, 32));
EXPECT_EQ(8, ImageHeader::CalculateDefaultPitch(1, 33));

EXPECT_EQ(4, ImageHeader::CalculatePitch(4, 1));
EXPECT_EQ(4, ImageHeader::CalculatePitch(4, 8));
EXPECT_EQ(8, ImageHeader::CalculatePitch(4, 9));
EXPECT_EQ(4, ImageHeader::CalculateDefaultPitch(4, 1));
EXPECT_EQ(4, ImageHeader::CalculateDefaultPitch(4, 8));
EXPECT_EQ(8, ImageHeader::CalculateDefaultPitch(4, 9));

EXPECT_EQ(4, ImageHeader::CalculatePitch(8, 1));
EXPECT_EQ(4, ImageHeader::CalculatePitch(8, 4));
EXPECT_EQ(8, ImageHeader::CalculatePitch(8, 5));
EXPECT_EQ(4, ImageHeader::CalculateDefaultPitch(8, 1));
EXPECT_EQ(4, ImageHeader::CalculateDefaultPitch(8, 4));
EXPECT_EQ(8, ImageHeader::CalculateDefaultPitch(8, 5));

// Test non-static version of function
ImageHeader imageHeader;
imageHeader.bitCount = 1;
imageHeader.width = 1;
EXPECT_EQ(4, imageHeader.CalculatePitch());
EXPECT_EQ(4, imageHeader.CalculateDefaultPitch());
}

TEST(ImageHeader, CalcByteWidth)
Expand Down
Loading