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 8 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
6 changes: 3 additions & 3 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 @@ -38,9 +38,9 @@ void BitmapFile::VerifyPixelSizeMatchesImageDimensionsWithPitch() const
BitmapFile::VerifyPixelSizeMatchesImageDimensionsWithPitch(imageHeader.bitCount, imageHeader.width, 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");
}
}
Expand Down
18 changes: 10 additions & 8 deletions src/Bitmap/BitmapFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,17 @@ class BitmapFile
std::vector<Color> palette;
std::vector<uint8_t> pixels;

// 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: Must include padding to fill each image row out to the next byte memory border (pitch). 4 byte pitch is typical.
Brett208 marked this conversation as resolved.
Show resolved Hide resolved
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;
Expand All @@ -41,7 +42,7 @@ class BitmapFile
// @width: Width in pixels. Do not include the pitch in width.
// @pixelsWithPitchSize: Number of pixels including padding pixels to next 4 byte boundary.
void VerifyPixelSizeMatchesImageDimensionsWithPitch() const;
static void VerifyPixelSizeMatchesImageDimensionsWithPitch(uint16_t bitCount, int32_t width, int32_t height, std::size_t pixelsWithPitchSize);
static void VerifyPixelSizeMatchesImageDimensionsWithPitch(uint16_t bitCount, std::size_t pitch, int32_t height, std::size_t pixelsWithPitchSize);

void Validate() const;

Expand All @@ -55,8 +56,9 @@ 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 std::size_t FindPitch(std::size_t width, std::size_t height, std::size_t pixelCount);
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
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 interval is 32
Brett208 marked this conversation as resolved.
Show resolved Hide resolved
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
37 changes: 26 additions & 11 deletions src/Bitmap/IndexedBmpWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,50 @@ 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)
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;

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

return pitch;
}

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 +67,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
38 changes: 32 additions & 6 deletions src/Sprite/OP2BmpLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,20 @@
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 +31,31 @@ 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) {
palette.push_back(Color{ 0, 0, 0 });
palette.push_back(Color{ 255, 255, 255 });
}
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
7 changes: 7 additions & 0 deletions src/Sprite/OP2BmpLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@ 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
Expand All @@ -20,5 +26,6 @@ class OP2BmpLoader
Stream::FileReader bmpReader;
ArtFile artFile;

std::vector<Color> CreatePalette(const ImageMeta& imageMeta) const;
std::unique_ptr<Stream::FileSliceReader> GetPixels(std::size_t startingIndex, std::size_t length);
};