Skip to content

Commit 33ba2ea

Browse files
author
Bim Overbohm
committed
Support grayscale images
1 parent 00dac0b commit 33ba2ea

File tree

5 files changed

+116
-47
lines changed

5 files changed

+116
-47
lines changed

src/img2h.cpp

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,10 @@ void printUsage()
169169
std::cout << "tiles, tilemap, delta8 / delta16, rle, lz10 / lz11, interleavepixels, output" << std::endl;
170170
}
171171

172-
std::tuple<Magick::ImageType, Magick::Geometry, std::vector<Image::Data>> readImages(const std::vector<std::string> &fileNames, const ProcessingOptions &options)
172+
std::tuple<bool, Magick::Geometry, std::vector<Image::Data>> readImages(const std::vector<std::string> &fileNames, const ProcessingOptions &options)
173173
{
174174
Magick::ImageType imgType = Magick::ImageType::UndefinedType;
175+
Magick::ClassType imgClass = Magick::ClassType::UndefinedClass;
175176
Magick::Geometry imgSize;
176177
std::vector<Image::Data> images;
177178
// open first image and store type
@@ -190,54 +191,53 @@ std::tuple<Magick::ImageType, Magick::Geometry, std::vector<Image::Data>> readIm
190191
}
191192
imgSize = img.size();
192193
std::cout << " -> " << imgSize.width() << "x" << imgSize.height() << ", ";
193-
const bool isGreyscale = img.classType() == Magick::ClassType::PseudoClass && img.type() == Magick::ImageType::GrayscaleType;
194+
imgType = img.type();
195+
imgClass = img.classType();
196+
const bool isGreyscale = imgClass == Magick::ClassType::PseudoClass && imgType == Magick::ImageType::GrayscaleType;
197+
const bool isPaletted = imgClass == Magick::ClassType::PseudoClass && imgType == Magick::ImageType::PaletteType;
194198
if (isGreyscale)
195199
{
196-
img.modifyImage();
197-
img.quantizeColors(256);
198-
img.quantizeDither(false);
199-
img.quantize();
200-
img.syncPixels();
201-
REQUIRE(img.classType() == Magick::ClassType::PseudoClass && img.type() == Magick::ImageType::PaletteType, std::runtime_error, "Image conversion failed");
202-
std::cout << "greyscale >> ";
200+
std::cout << "greyscale" << std::endl;
203201
}
204-
imgType = img.type();
205-
const bool isPaletted = img.classType() == Magick::ClassType::PseudoClass && imgType == Magick::ImageType::PaletteType;
206-
if (isPaletted)
202+
else if (isPaletted)
207203
{
208204
std::cout << "paletted, " << img.colorMapSize() << " colors" << std::endl;
209205
}
210-
else if (imgType == Magick::ImageType::TrueColorType)
206+
else if (imgType == Magick::ImageType::TrueColorType || imgType == Magick::ImageType::TrueColorAlphaType)
211207
{
212-
std::cout << "true color" << std::endl;
208+
std::cout << "true color" << (imgType == Magick::ImageType::TrueColorAlphaType ? " (Warning: Alpha ignored)" : "") << std::endl;
213209
}
214210
else
215211
{
216-
THROW(std::runtime_error, "Unsupported image format. ClassType " << img.classType() << ", ImageType " << img.type());
212+
THROW(std::runtime_error, "Unsupported image format. ClassType " << classTypeToString(img.classType()) << ", ImageType " << imageTypeToString(img.type()));
217213
}
218214
// compare size and type to first image to make sure all images have the same format
219215
if (images.size() > 0)
220216
{
221217
// check type and size
222218
REQUIRE(images.front().type == imgType, std::runtime_error, "Image types do not match");
219+
REQUIRE(images.front().classType == imgClass, std::runtime_error, "Image class types do not match");
223220
REQUIRE(images.front().size == imgSize, std::runtime_error, "Image sizes do not match");
224221
}
222+
const auto isNotDirect = isGreyscale | isPaletted;
225223
// if we want to convert to tiles or sprites make sure data is multiple of 8 pixels in width and height
226-
if ((options.sprites || options.tiles) && (!isPaletted || imgSize.width() % 8 != 0 || imgSize.height() % 8 != 0))
224+
if ((options.sprites || options.tiles) && (!isNotDirect || imgSize.width() % 8 != 0 || imgSize.height() % 8 != 0))
227225
{
228226
THROW(std::runtime_error, "Image must be paletted and width / height must be a multiple of 8");
229227
}
230228
if (options.sprites && (imgSize.width() % options.sprites.value.front() != 0 || imgSize.height() % options.sprites.value.back() != 0))
231229
{
232230
THROW(std::runtime_error, "Image width / height must be a multiple of sprite width / height");
233231
}
234-
auto imgPalette = isPaletted ? getColorMap(img) : std::vector<Magick::Color>();
235-
auto imgData = isPaletted ? getImageData(img) : toRGB555(getImageData(img));
236-
Image::Data entry{static_cast<uint32_t>(std::distance(fileNames.cbegin(), ifIt)), *ifIt, imgType, imgSize, Image::DataType::Bitmap, (isPaletted ? Image::ColorFormat::Paletted8 : Image::ColorFormat::RGB555), {}, imgData, imgPalette};
232+
auto imgData = isNotDirect ? getImageData(img) : toRGB555(getImageData(img));
233+
auto imgPalette = isNotDirect ? getColorMap(img) : std::vector<Magick::Color>();
234+
Image::Data entry{static_cast<uint32_t>(std::distance(fileNames.cbegin(), ifIt)), *ifIt, imgType, imgClass, imgSize, Image::DataType::Bitmap, (isNotDirect ? Image::ColorFormat::Paletted8 : Image::ColorFormat::RGB555), {}, imgData, imgPalette};
237235
images.push_back(entry);
238236
ifIt++;
239237
}
240-
return {imgType, imgSize, images};
238+
// we consider greyscale images as paletted
239+
const bool isPaletted = (imgClass == Magick::ClassType::PseudoClass && imgType == Magick::ImageType::GrayscaleType) || (imgClass == Magick::ClassType::PseudoClass && imgType == Magick::ImageType::PaletteType);
240+
return {isPaletted, imgSize, images};
241241
}
242242

243243
std::string getBaseNameFromFilePath(const std::string &filePath)
@@ -272,7 +272,7 @@ int main(int argc, const char *argv[])
272272
// fire up ImageMagick
273273
Magick::InitializeMagick(*argv);
274274
// read image(s) from disk
275-
auto [imgType, imgSize, images] = readImages(m_inFile, options);
275+
auto [imgIsPaletted, imgSize, images] = readImages(m_inFile, options);
276276
// build processing pipeline
277277
Image::Processing processing;
278278
if (options.reorderColors)
@@ -291,7 +291,7 @@ int main(int argc, const char *argv[])
291291
{
292292
processing.addStep(Image::ProcessingType::ShiftIndices, {options.shiftIndices.value});
293293
}
294-
if (imgType == Magick::ImageType::PaletteType)
294+
if (imgIsPaletted)
295295
{
296296
if (images.size() > 1)
297297
{
@@ -344,7 +344,7 @@ int main(int argc, const char *argv[])
344344
// check if all color maps are the same
345345
bool allColorMapsSame = true;
346346
uint32_t maxColorMapColors = 0;
347-
if (imgType == Magick::ImageType::PaletteType)
347+
if (imgIsPaletted)
348348
{
349349
if (images.size() == 1)
350350
{
@@ -401,7 +401,7 @@ int main(int argc, const char *argv[])
401401
imgSize = Magick::Geometry(8, 8);
402402
}
403403
}
404-
if (imgType == Magick::ImageType::PaletteType)
404+
if (imgIsPaletted)
405405
{
406406
nrOfBytesPerImageOrSprite = maxColorMapColors <= 16 ? (nrOfBytesPerImageOrSprite / 2) : nrOfBytesPerImageOrSprite;
407407
nrOfBytesPerImageOrSprite = maxColorMapColors <= 4 ? (nrOfBytesPerImageOrSprite / 2) : nrOfBytesPerImageOrSprite;
@@ -428,7 +428,7 @@ int main(int argc, const char *argv[])
428428
writeImageInfoToH(hFile, varName, imageData32, {}, imgSize.width(), imgSize.height(), nrOfBytesPerImageOrSprite, nrOfImagesOrSprites, storeTileOrSpriteWise);
429429
writeImageDataToC(cFile, varName, baseName, imageData32, imageOrSpriteStartIndices, {}, storeTileOrSpriteWise);
430430
}
431-
if (imgType == Magick::ImageType::PaletteType)
431+
if (imgIsPaletted)
432432
{
433433
auto [paletteData16, colorMapsStartIndices] = (allColorMapsSame ? std::make_pair(convertToBGR555(images.front().colorMap), std::vector<uint32_t>()) : Image::Processing::combineColorMaps<uint16_t>(images, [](auto cm)
434434
{ return convertToBGR555(cm); }));

src/processing/imagehelpers.cpp

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,46 @@
1717
#error "ImageMagick is a pile of shit"
1818
#endif
1919

20+
std::string imageTypeToString(const Magick::ImageType type)
21+
{
22+
std::array<std::string, 11> imageTypes = {"BilevelType", "GrayscaleType", "GrayscaleAlphaType", "PaletteType", "PaletteAlphaType", "TrueColorType",
23+
"TrueColorAlphaType", "ColorSeparationType", "ColorSeparationAlphaType", "OptimizeType", "PaletteBilevelAlphaType"};
24+
auto index = ((uint32_t)type);
25+
if (index > 0 && index <= imageTypes.size())
26+
{
27+
return imageTypes[index - 1];
28+
}
29+
return "unknown";
30+
}
31+
32+
std::string classTypeToString(const Magick::ClassType type)
33+
{
34+
std::string classTypeString;
35+
std::array<std::string, 2> classTypes = {"DirectClass", "PseudoClass"};
36+
auto index = ((uint32_t)type);
37+
if (index > 0 && index <= classTypes.size())
38+
{
39+
return classTypes[index - 1];
40+
}
41+
return "unknown";
42+
}
43+
2044
std::vector<uint8_t> getImageData(const Magick::Image &img)
2145
{
2246
std::vector<uint8_t> data;
23-
if (img.classType() == Magick::ClassType::PseudoClass && img.type() == Magick::ImageType::PaletteType)
47+
if (img.classType() == Magick::ClassType::PseudoClass && img.type() == Magick::ImageType::GrayscaleType)
48+
{
49+
// get GrayChannel from image as unsigned chars
50+
auto temp = img.separate(MagickCore::GrayChannel);
51+
auto indices = temp.getConstPixels(0, 0, temp.columns(), temp.rows());
52+
REQUIRE(indices != nullptr, std::runtime_error, "Failed to get grayscale image pixels");
53+
const auto nrOfIndices = temp.columns() * temp.rows();
54+
for (std::remove_const<decltype(nrOfIndices)>::type i = 0; i < nrOfIndices; i++)
55+
{
56+
data.push_back(static_cast<uint8_t>(std::round(255.0F * indices[i]) / QuantumRange));
57+
}
58+
}
59+
else if (img.classType() == Magick::ClassType::PseudoClass && img.type() == Magick::ImageType::PaletteType)
2460
{
2561
// get palette indices as unsigned chars
2662
const auto nrOfColors = img.colorMapSize();
@@ -32,10 +68,11 @@ std::vector<uint8_t> getImageData(const Magick::Image &img)
3268
const auto nrOfIndices = temp.columns() * temp.rows();
3369
for (std::remove_const<decltype(nrOfIndices)>::type i = 0; i < nrOfIndices; i++)
3470
{
71+
REQUIRE(indices[i] <= 255, std::runtime_error, "Image color index must be <= 255");
3572
data.push_back(static_cast<uint8_t>(indices[i]));
3673
}
3774
}
38-
else if (img.classType() == Magick::ClassType::DirectClass && img.type() == Magick::ImageType::TrueColorType)
75+
else if (img.classType() == Magick::ClassType::DirectClass && (img.type() == Magick::ImageType::TrueColorType || img.type() == Magick::ImageType::TrueColorAlphaType))
3976
{
4077
// get pixel colors as RGB888
4178
auto pixels = img.getConstPixels(0, 0, img.columns(), img.rows());
@@ -46,6 +83,11 @@ std::vector<uint8_t> getImageData(const Magick::Image &img)
4683
data.push_back(static_cast<uint8_t>(std::round(255.0F * *pixels++) / QuantumRange));
4784
data.push_back(static_cast<uint8_t>(std::round(255.0F * *pixels++) / QuantumRange));
4885
data.push_back(static_cast<uint8_t>(std::round(255.0F * *pixels++) / QuantumRange));
86+
// ignore alpha channel pixels
87+
if (img.type() == Magick::ImageType::TrueColorAlphaType)
88+
{
89+
pixels++;
90+
}
4991
}
5092
}
5193
else
@@ -60,12 +102,27 @@ std::vector<uint8_t> getImageData(const Magick::Image &img)
60102

61103
std::vector<Magick::Color> getColorMap(const Magick::Image &img)
62104
{
63-
const auto nrOfColors = img.colorMapSize();
64-
REQUIRE(nrOfColors <= 256, std::runtime_error, "Only up to 256 colors supported in color map");
65-
std::vector<Magick::Color> colorMap(nrOfColors);
66-
for (std::remove_const<decltype(nrOfColors)>::type i = 0; i < nrOfColors; ++i)
105+
std::vector<Magick::Color> colorMap;
106+
if (img.classType() == Magick::ClassType::PseudoClass && img.type() == Magick::ImageType::GrayscaleType)
107+
{
108+
for (std::vector<Magick::Color>::size_type i = 0; i < 256; ++i)
109+
{
110+
double grayValue = i / 255.0;
111+
colorMap.push_back(Magick::ColorRGB(grayValue, grayValue, grayValue));
112+
}
113+
}
114+
else if (img.classType() == Magick::ClassType::PseudoClass && img.type() == Magick::ImageType::PaletteType)
67115
{
68-
colorMap[i] = img.colorMap(i);
116+
const auto nrOfColors = img.colorMapSize();
117+
REQUIRE(nrOfColors <= 256, std::runtime_error, "Only up to 256 colors supported in color map");
118+
for (std::remove_const<decltype(nrOfColors)>::type i = 0; i < nrOfColors; ++i)
119+
{
120+
colorMap.push_back(img.colorMap(i));
121+
}
122+
}
123+
else
124+
{
125+
throw std::runtime_error("Unsupported image type!");
69126
}
70127
return colorMap;
71128
}

src/processing/imagehelpers.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,20 @@
22
#pragma once
33

44
#include <cstdint>
5+
#include <string>
56
#include <vector>
67
#include <Magick++.h>
78

9+
/// @brief Convert image type to string
10+
/// @param type Input type
11+
/// @return Image type or "unknown" if not valid
12+
std::string imageTypeToString(const Magick::ImageType type);
13+
14+
/// @brief Convert image class type to string
15+
/// @param type Input type
16+
/// @return Image class type or "unknown" if not valid
17+
std::string classTypeToString(const Magick::ClassType type);
18+
819
/// @brief Get ImageMagick image data (palette or truecolor) as raw data bytes
920
/// The format returned are Palette8 for paletted images and RGB888 for truecolor images
1021
std::vector<uint8_t> getImageData(const Magick::Image &img);

src/processing/imageprocessing.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ namespace Image
5656
temp.quantizeColors(2);
5757
temp.type(Magick::ImageType::PaletteType);
5858
// get image data and color map
59-
return {0, "", temp.type(), image.size(), DataType::Bitmap, ColorFormat::Paletted8, {}, getImageData(temp), getColorMap(temp), ColorFormat::Unknown, {}};
59+
return {0, "", temp.type(), temp.classType(), image.size(), DataType::Bitmap, ColorFormat::Paletted8, {}, getImageData(temp), getColorMap(temp), ColorFormat::Unknown, {}};
6060
}
6161

6262
Data Processing::toPaletted(const Magick::Image &image, const std::vector<Parameter> &parameters, Statistics::Container::SPtr statistics)
@@ -75,7 +75,7 @@ namespace Image
7575
temp.quantizeColors(nrOfcolors);
7676
temp.type(Magick::ImageType::PaletteType);
7777
// get image data and color map
78-
return {0, "", temp.type(), image.size(), DataType::Bitmap, ColorFormat::Paletted8, {}, getImageData(temp), getColorMap(temp), ColorFormat::Unknown, {}};
78+
return {0, "", temp.type(), temp.classType(), image.size(), DataType::Bitmap, ColorFormat::Paletted8, {}, getImageData(temp), getColorMap(temp), ColorFormat::Unknown, {}};
7979
}
8080

8181
Data Processing::toTruecolor(const Magick::Image &image, const std::vector<Parameter> &parameters, Statistics::Container::SPtr statistics)
@@ -109,7 +109,7 @@ namespace Image
109109
{
110110
imageData = toRGB565(imageData);
111111
}
112-
return {0, "", temp.type(), image.size(), DataType::Bitmap, format, {}, imageData, {}, ColorFormat::Unknown, {}};
112+
return {0, "", temp.type(), temp.classType(), image.size(), DataType::Bitmap, format, {}, imageData, {}, ColorFormat::Unknown, {}};
113113
}
114114

115115
// ----------------------------------------------------------------------------

src/processing/imagestructs.h

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,19 @@ namespace Image
4141
/// @brief Stores data for an image
4242
struct Data
4343
{
44-
uint32_t index = 0; // image index counter
45-
std::string fileName; // input file name
46-
Magick::ImageType type = Magick::ImageType::UndefinedType; // input image type
47-
Magick::Geometry size = {0, 0}; // image size
48-
DataType dataType = DataType::Unknown; // image data type
49-
ColorFormat colorFormat = ColorFormat::Unknown; // image color format
50-
std::vector<uint16_t> mapData; // raw screen / map data (only if dataType == Tilemap)
51-
std::vector<uint8_t> data; // raw image / bitmap / tile data
52-
std::vector<Magick::Color> colorMap; // image color map if paletted
53-
ColorFormat colorMapFormat = ColorFormat::Unknown; // raw color map data format
54-
std::vector<uint8_t> colorMapData; // raw color map data
55-
uint32_t maxMemoryNeeded = 0; // max. intermediate memory needed to process the image. 0 if it can be directly written to destination (single processing stage)
44+
uint32_t index = 0; // image index counter
45+
std::string fileName; // input file name
46+
Magick::ImageType type = Magick::ImageType::UndefinedType; // input image type
47+
Magick::ClassType classType = Magick::ClassType::UndefinedClass; // input image class
48+
Magick::Geometry size = {0, 0}; // image size
49+
DataType dataType = DataType::Unknown; // image data type
50+
ColorFormat colorFormat = ColorFormat::Unknown; // image color format
51+
std::vector<uint16_t> mapData; // raw screen / map data (only if dataType == Tilemap)
52+
std::vector<uint8_t> data; // raw image / bitmap / tile data
53+
std::vector<Magick::Color> colorMap; // image color map if paletted
54+
ColorFormat colorMapFormat = ColorFormat::Unknown; // raw color map data format
55+
std::vector<uint8_t> colorMapData; // raw color map data
56+
uint32_t maxMemoryNeeded = 0; // max. intermediate memory needed to process the image. 0 if it can be directly written to destination (single processing stage)
5657
};
5758

5859
/// @brief Return true if the data has a color map, false if not.

0 commit comments

Comments
 (0)