diff --git a/Makefile b/Makefile index 0c8d0bb..418e223 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,15 @@ all: piphoto -piphoto: *.cc *.h Makefile - clang-3.9 -O3 -g -Weverything -Werror -Wno-c++98-compat -Wno-c++98-c++11-compat-pedantic --std=c++1z --stdlib=libc++ -o piphoto piphoto.cc -lc++ -lunwind -lpng +objects = piphoto.o coord.o util.o + +piphoto: $(objects) Makefile + clang-3.9 -O3 -g -Weverything -Werror -Wno-c++98-compat -Wno-c++98-c++11-compat-pedantic --std=c++1z --stdlib=libc++ -o piphoto $(objects) -lc++ -lunwind -lpng + +%.o: %.cc *.h Makefile + clang-3.9 -O3 -g -Weverything -Werror -Wno-c++98-compat -Wno-c++98-c++11-compat-pedantic --std=c++1z --stdlib=libc++ -c -o $@ $< run: piphoto ./piphoto clean: - rm -f piphoto + rm -f piphoto *.o diff --git a/color.h b/color.h index 78190bf..7b0ac50 100644 --- a/color.h +++ b/color.h @@ -1,5 +1,7 @@ #pragma once +#include + struct Color { // 32-bit for compiler convenience, but values are 16-bit uint32_t r; diff --git a/colorchecker.h b/colorchecker.h index fc544fe..cffe702 100644 --- a/colorchecker.h +++ b/colorchecker.h @@ -1,5 +1,11 @@ #pragma once +#include + +#include "color.h" +#include "coord.h" +#include "image.h" + constexpr std::array kColorCheckerSrgb = {{ {0x7300, 0x5200, 0x4400}, {0xc200, 0x9600, 0x8200}, @@ -26,3 +32,28 @@ constexpr std::array kColorCheckerSrgb = {{ {0x5500, 0x5500, 0x5500}, {0x3400, 0x3400, 0x3400}, }}; + +template +std::array ColorCheckerClosest(const Image& image) { + std::array closest; + std::array diff; + diff.fill(UINT32_MAX); + + for (uint32_t y = 0; y < Y; ++y) { + const auto& row = image.at(y); + + for (uint32_t x = 0; x < X; ++x) { + const auto& pixel = row.at(x); + + for (uint32_t cc = 0; cc < kColorCheckerSrgb.size(); ++cc) { + auto pixel_diff = pixel.Difference(kColorCheckerSrgb.at(cc)); + if (pixel_diff < diff.at(cc)) { + diff.at(cc) = pixel_diff; + closest.at(cc) = {x, y}; + } + } + } + } + + return closest; +} diff --git a/colors.h b/colors.h index 04edd16..0ac1671 100644 --- a/colors.h +++ b/colors.h @@ -1,4 +1,6 @@ #pragma once +#include "color.h" + constexpr Color kBlack = Color{0x0000, 0x0000, 0x0000}; constexpr Color kWhite = Color{0xffff, 0xffff, 0xffff}; diff --git a/coord.cc b/coord.cc new file mode 100644 index 0000000..94fa9d6 --- /dev/null +++ b/coord.cc @@ -0,0 +1,6 @@ +#include "coord.h" + +std::ostream& operator<<(std::ostream& os, const Coord& coord) { + return os << "(" << coord.x << ", " << coord.y << ")"; +} + diff --git a/coord.h b/coord.h new file mode 100644 index 0000000..0bd822d --- /dev/null +++ b/coord.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +struct Coord { + uint32_t x; + uint32_t y; +}; + +std::ostream& operator<<(std::ostream& os, const Coord& coord); diff --git a/image.h b/image.h new file mode 100644 index 0000000..1fa381d --- /dev/null +++ b/image.h @@ -0,0 +1,55 @@ +#pragma once + +#include "color.h" +#include "coord.h" + +template +class Image : public std::array, Y> { + public: + constexpr const Color& GetPixel(const Coord& coord) const; + + void SetPixel(const Coord& coord, const Color& color); + void DrawXLine(const Coord& start, const Color& color, uint32_t length); + void DrawYLine(const Coord& start, const Color& color, uint32_t length); + void DrawRectangle(const Coord& start, const Color& color, uint32_t x_length, uint32_t y_length); + void DrawSquare(const Coord& start, const Color& color, uint32_t length); +}; + +template +constexpr const Color& Image::GetPixel(const Coord& coord) const { + return this->at(coord.y).at(coord.x); +} + +template +void Image::SetPixel(const Coord& coord, const Color& color) { + this->at(coord.y).at(coord.x) = color; +} + +template +void Image::DrawXLine(const Coord& coord, const Color& color, uint32_t length) { + auto& row = this->at(coord.y); + + for (uint32_t x = coord.x; x < std::min(X, coord.x + length); ++x) { + row.at(x) = color; + } +} + +template +void Image::DrawYLine(const Coord& coord, const Color& color, uint32_t length) { + for (uint32_t y = coord.y; y <= std::min(Y, coord.y + length); ++y) { + SetPixel({coord.x, y}, color); + } +} + +template +void Image::DrawRectangle(const Coord& start, const Color& color, uint32_t x_length, uint32_t y_length) { + DrawXLine(start, color, x_length); + DrawXLine({start.x, start.y + y_length}, color, x_length); + DrawYLine(start, color, y_length); + DrawYLine({start.x + x_length, start.y}, color, y_length); +} + +template +void Image::DrawSquare(const Coord& start, const Color& color, uint32_t length) { + DrawRectangle(start, color, length, length); +} diff --git a/piphoto.cc b/piphoto.cc index c2de5c6..4a500e9 100644 --- a/piphoto.cc +++ b/piphoto.cc @@ -1,314 +1,17 @@ -#include -#include -#include -#include -#include -#include -#include - -#include -#include #include -#include #include "color.h" #include "colorchecker.h" #include "colors.h" - -namespace std { -using string_view = experimental::string_view; -} - -std::string ReadFile(const std::string& filename); -void WriteFile(const std::string& filename, const std::string& contents); - -struct Coord { - uint32_t x; - uint32_t y; -}; - -std::ostream& operator<<(std::ostream& os, const Coord& coord); - -std::ostream& operator<<(std::ostream& os, const Coord& coord) { - return os << "(" << coord.x << ", " << coord.y << ")"; -} - -template -class Image : public std::array, Y> { - public: - std::array ColorCheckerClosest() const; - - Color GetPixel(const Coord& coord) const; - - void SetPixel(const Coord& coord, const Color& color); - void DrawXLine(const Coord& start, const Color& color, uint32_t length); - void DrawYLine(const Coord& start, const Color& color, uint32_t length); - void DrawRectangle(const Coord& start, const Color& color, uint32_t x_length, uint32_t y_length); - void DrawSquare(const Coord& start, const Color& color, uint32_t length); -}; - -template -std::array Image::ColorCheckerClosest() const { - std::array closest; - std::array diff; - diff.fill(UINT32_MAX); - - for (uint32_t y = 0; y < Y; ++y) { - const auto& row = this->at(y); - - for (uint32_t x = 0; x < X; ++x) { - const auto& pixel = row.at(x); - - for (uint32_t cc = 0; cc < kColorCheckerSrgb.size(); ++cc) { - auto pixel_diff = pixel.Difference(kColorCheckerSrgb.at(cc)); - if (pixel_diff < diff.at(cc)) { - diff.at(cc) = pixel_diff; - closest.at(cc) = {x, y}; - } - } - } - } - - return closest; -} - -template -Color Image::GetPixel(const Coord& coord) const { - return this->at(coord.y).at(coord.x); -} - -template -void Image::SetPixel(const Coord& coord, const Color& color) { - this->at(coord.y).at(coord.x) = color; -} - -template -void Image::DrawXLine(const Coord& coord, const Color& color, uint32_t length) { - auto& row = this->at(coord.y); - - for (uint32_t x = coord.x; x < std::min(X, coord.x + length); ++x) { - row.at(x) = color; - } -} - -template -void Image::DrawYLine(const Coord& coord, const Color& color, uint32_t length) { - for (uint32_t y = coord.y; y <= std::min(Y, coord.y + length); ++y) { - SetPixel({coord.x, y}, color); - } -} - -template -void Image::DrawRectangle(const Coord& start, const Color& color, uint32_t x_length, uint32_t y_length) { - DrawXLine(start, color, x_length); - DrawXLine({start.x, start.y + y_length}, color, x_length); - DrawYLine(start, color, y_length); - DrawYLine({start.x + x_length, start.y}, color, y_length); -} - -template -void Image::DrawSquare(const Coord& start, const Color& color, uint32_t length) { - DrawRectangle(start, color, length, length); -} - - -template -class PiRaw { - public: - PiRaw(std::unique_ptr>); - static PiRaw FromJpeg(const std::string_view& jpeg); - static PiRaw FromRaw(const std::string_view& raw); - - std::string ToPng(); - - Image* GetImage(); - const Image& GetImage() const; - - private: - static constexpr uint32_t kJpegHeaderBytes = 32768; - static constexpr const char* kJpegHeaderMagic = "BRCM"; - static constexpr uint32_t kPixelsPerChunk = 4; - static constexpr uint32_t kBitsPerByte = 8; - - static constexpr uint32_t GetRawBytes(); - static constexpr uint32_t GetRowBytes(); - static constexpr uint32_t GetNumRows(); - static constexpr uint32_t GetChunkBytes(); - - static constexpr uint32_t Align(uint32_t val); - - typedef std::array Chunk; - - static Chunk GetChunk(const std::string_view& raw, const uint32_t x_chunk, const uint32_t y); - static Color CombineRaw(uint32_t y0x0, uint32_t y0x1, uint32_t y1x0, uint32_t y1x1); - - std::unique_ptr> image_; -}; - -typedef PiRaw<3280, 2464, 10, 16, 2> PiRaw2; - -template -PiRaw::PiRaw(std::unique_ptr> image) - : image_(std::move(image)) {} - -template -PiRaw PiRaw::FromJpeg(const std::string_view& jpeg) { - auto container_len = GetRawBytes() + kJpegHeaderBytes; - assert(jpeg.substr(jpeg.size() - container_len, 4) == kJpegHeaderMagic); - return FromRaw(jpeg.substr(jpeg.size() - GetRawBytes(), GetRawBytes())); -} - -template -PiRaw PiRaw::FromRaw(const std::string_view& raw) { - static_assert(X % 2 == 0); - static_assert(Y % 2 == 0); - static_assert(kPixelsPerChunk == 4); - - assert(raw.size() == GetRawBytes()); - - auto image = std::make_unique>(); - - for (uint32_t y = 0, out_y = 0; y < Y; y += 2, ++out_y) { - for (uint32_t x_chunk = 0, out_x = 0; x_chunk < X / kPixelsPerChunk; ++x_chunk, out_x += kPixelsPerChunk / 2) { - auto chunk1 = GetChunk(raw, x_chunk, y + 0); - auto chunk2 = GetChunk(raw, x_chunk, y + 1); - image->at(out_y).at(out_x + 0) = CombineRaw(chunk1.at(0), chunk1.at(1), chunk2.at(0), chunk2.at(1)); - image->at(out_y).at(out_x + 1) = CombineRaw(chunk1.at(2), chunk1.at(3), chunk2.at(2), chunk2.at(3)); - } - } - return PiRaw(std::move(image)); -} - -template -constexpr uint32_t PiRaw::GetRawBytes() { - return GetRowBytes() * GetNumRows(); -} - -template -constexpr uint32_t PiRaw::GetRowBytes() { - return Align(Align(X + P) * D / kBitsPerByte); -} - -template -constexpr uint32_t PiRaw::GetNumRows() { - return Align(Y + P); -} - -template -constexpr uint32_t PiRaw::GetChunkBytes() { - return D * kPixelsPerChunk / kBitsPerByte; -} - -template -constexpr uint32_t PiRaw::Align(uint32_t val) { - return (~(A - 1)) & ((val) + (A - 1)); -} - -template -typename PiRaw::Chunk PiRaw::GetChunk(const std::string_view& raw, const uint32_t x_chunk, const uint32_t y) { - // Function is bit depth & layout specific - static_assert(D == 10); - - auto start = y * GetRowBytes() + x_chunk * GetChunkBytes(); - auto high0 = static_cast(raw.at(start + 0)); - auto high1 = static_cast(raw.at(start + 1)); - auto high2 = static_cast(raw.at(start + 2)); - auto high3 = static_cast(raw.at(start + 3)); - auto packed_low = static_cast(raw.at(start + 4)); - - Chunk ret; - ret.at(0) = ((high0 << 2) | ((packed_low >> 6) & 0b11)) << 6; - ret.at(1) = ((high1 << 2) | ((packed_low >> 4) & 0b11)) << 6; - ret.at(2) = ((high2 << 2) | ((packed_low >> 2) & 0b11)) << 6; - ret.at(3) = ((high3 << 2) | ((packed_low >> 0) & 0b11)) << 6; - return ret; -} - -template -Color PiRaw::CombineRaw(uint32_t y0x0, uint32_t y0x1, uint32_t y1x0, uint32_t y1x1) { - // Function is bit layout specific - Color ret; - ret.r = y1x1; - ret.g = (y0x1 + y1x0) / 2; - ret.b = y0x0; - return ret; -} - -static void WriteCallback(png_structp png_ptr, png_bytep data, png_size_t length) { - auto dest = static_cast(png_get_io_ptr(png_ptr)); - dest->append(reinterpret_cast(data), length); -} - -template -std::string PiRaw::ToPng() { - std::string ret; - - auto png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - assert(png_ptr); - auto info_ptr = png_create_info_struct(png_ptr); - assert(info_ptr); - - png_set_write_fn(png_ptr, &ret, &WriteCallback, nullptr); - png_set_IHDR(png_ptr, info_ptr, X / 2, Y / 2, - 16, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - - png_write_info(png_ptr, info_ptr); - for (auto& row : *image_) { - std::array out_row; - for (uint32_t x = 0; x < X; ++x) { - out_row[x * 3 + 0] = htons(static_cast(row[x].r)); - out_row[x * 3 + 1] = htons(static_cast(row[x].g)); - out_row[x * 3 + 2] = htons(static_cast(row[x].b)); - } - png_write_row(png_ptr, reinterpret_cast(out_row.data())); - } - png_write_end(png_ptr, nullptr); - - png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); - png_destroy_write_struct(&png_ptr, &info_ptr); - - return ret; -} - -template -Image* PiRaw::GetImage() { - return image_.get(); -} - -template -const Image& PiRaw::GetImage() const { - return *image_; -} - -std::string ReadFile(const std::string& filename) { - int fh = open(filename.c_str(), O_RDONLY); - assert(fh != -1); - - struct stat st; - assert(fstat(fh, &st) == 0); - - std::string contents; - contents.resize(static_cast(st.st_size)); - - assert(read(fh, &contents[0], static_cast(st.st_size)) == st.st_size); - assert(close(fh) == 0); - - return contents; -} - -void WriteFile(const std::string& filename, const std::string& contents) { - int fh = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); - assert(fh != -1); - assert(write(fh, &contents[0], contents.size()) == static_cast(contents.size())); - assert(close(fh) == 0); -} - +#include "coord.h" +#include "image.h" +#include "piraw.h" +#include "util.h" int main() { auto raw = PiRaw2::FromJpeg(ReadFile("test.jpg")); auto* image = raw.GetImage(); - auto closest = image->ColorCheckerClosest(); + auto closest = ColorCheckerClosest(*image); for (uint32_t cc = 0; cc < kColorCheckerSrgb.size(); ++cc) { const auto& coord = closest.at(cc); const auto& color = kColorCheckerSrgb.at(cc); diff --git a/piraw.h b/piraw.h new file mode 100644 index 0000000..18d7ce8 --- /dev/null +++ b/piraw.h @@ -0,0 +1,183 @@ +#pragma once + +#include +#include + +#include +#include + +#include "color.h" +#include "image.h" + +namespace std { +using string_view = experimental::string_view; +} + +template +class PiRaw { + public: + PiRaw(std::unique_ptr>); + static PiRaw FromJpeg(const std::string_view& jpeg); + static PiRaw FromRaw(const std::string_view& raw); + + std::string ToPng(); + + Image* GetImage(); + const Image& GetImage() const; + + private: + static constexpr uint32_t kJpegHeaderBytes = 32768; + static constexpr const char* kJpegHeaderMagic = "BRCM"; + static constexpr uint32_t kPixelsPerChunk = 4; + static constexpr uint32_t kBitsPerByte = 8; + + static constexpr uint32_t GetRawBytes(); + static constexpr uint32_t GetRowBytes(); + static constexpr uint32_t GetNumRows(); + static constexpr uint32_t GetChunkBytes(); + + static constexpr uint32_t Align(uint32_t val); + + typedef std::array Chunk; + + static Chunk GetChunk(const std::string_view& raw, const uint32_t x_chunk, const uint32_t y); + static Color CombineRaw(uint32_t y0x0, uint32_t y0x1, uint32_t y1x0, uint32_t y1x1); + + std::unique_ptr> image_; +}; + +typedef PiRaw<3280, 2464, 10, 16, 2> PiRaw2; + +template +PiRaw::PiRaw(std::unique_ptr> image) + : image_(std::move(image)) {} + +template +PiRaw PiRaw::FromJpeg(const std::string_view& jpeg) { + auto container_len = GetRawBytes() + kJpegHeaderBytes; + assert(jpeg.substr(jpeg.size() - container_len, 4) == kJpegHeaderMagic); + return FromRaw(jpeg.substr(jpeg.size() - GetRawBytes(), GetRawBytes())); +} + +template +PiRaw PiRaw::FromRaw(const std::string_view& raw) { + static_assert(X % 2 == 0); + static_assert(Y % 2 == 0); + static_assert(kPixelsPerChunk == 4); + + assert(raw.size() == GetRawBytes()); + + auto image = std::make_unique>(); + + for (uint32_t y = 0, out_y = 0; y < Y; y += 2, ++out_y) { + for (uint32_t x_chunk = 0, out_x = 0; x_chunk < X / kPixelsPerChunk; ++x_chunk, out_x += kPixelsPerChunk / 2) { + auto chunk1 = GetChunk(raw, x_chunk, y + 0); + auto chunk2 = GetChunk(raw, x_chunk, y + 1); + image->at(out_y).at(out_x + 0) = CombineRaw(chunk1.at(0), chunk1.at(1), chunk2.at(0), chunk2.at(1)); + image->at(out_y).at(out_x + 1) = CombineRaw(chunk1.at(2), chunk1.at(3), chunk2.at(2), chunk2.at(3)); + } + } + return PiRaw(std::move(image)); +} + +template +constexpr uint32_t PiRaw::GetRawBytes() { + return GetRowBytes() * GetNumRows(); +} + +template +constexpr uint32_t PiRaw::GetRowBytes() { + return Align(Align(X + P) * D / kBitsPerByte); +} + +template +constexpr uint32_t PiRaw::GetNumRows() { + return Align(Y + P); +} + +template +constexpr uint32_t PiRaw::GetChunkBytes() { + return D * kPixelsPerChunk / kBitsPerByte; +} + +template +constexpr uint32_t PiRaw::Align(uint32_t val) { + return (~(A - 1)) & ((val) + (A - 1)); +} + +template +typename PiRaw::Chunk PiRaw::GetChunk(const std::string_view& raw, const uint32_t x_chunk, const uint32_t y) { + // Function is bit depth & layout specific + static_assert(D == 10); + + auto start = y * GetRowBytes() + x_chunk * GetChunkBytes(); + auto high0 = static_cast(raw.at(start + 0)); + auto high1 = static_cast(raw.at(start + 1)); + auto high2 = static_cast(raw.at(start + 2)); + auto high3 = static_cast(raw.at(start + 3)); + auto packed_low = static_cast(raw.at(start + 4)); + + Chunk ret; + ret.at(0) = ((high0 << 2) | ((packed_low >> 6) & 0b11)) << 6; + ret.at(1) = ((high1 << 2) | ((packed_low >> 4) & 0b11)) << 6; + ret.at(2) = ((high2 << 2) | ((packed_low >> 2) & 0b11)) << 6; + ret.at(3) = ((high3 << 2) | ((packed_low >> 0) & 0b11)) << 6; + return ret; +} + +template +Color PiRaw::CombineRaw(uint32_t y0x0, uint32_t y0x1, uint32_t y1x0, uint32_t y1x1) { + // Function is bit layout specific + Color ret; + ret.r = y1x1; + ret.g = (y0x1 + y1x0) / 2; + ret.b = y0x0; + return ret; +} + +static void WriteCallback(png_structp png_ptr, png_bytep data, png_size_t length) { + auto dest = static_cast(png_get_io_ptr(png_ptr)); + dest->append(reinterpret_cast(data), length); +} + +template +std::string PiRaw::ToPng() { + std::string ret; + + auto png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + assert(png_ptr); + auto info_ptr = png_create_info_struct(png_ptr); + assert(info_ptr); + + png_set_write_fn(png_ptr, &ret, &WriteCallback, nullptr); + png_set_IHDR(png_ptr, info_ptr, X / 2, Y / 2, + 16, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + png_write_info(png_ptr, info_ptr); + for (auto& row : *image_) { + std::array out_row; + for (uint32_t x = 0; x < X; ++x) { + out_row[x * 3 + 0] = htons(static_cast(row[x].r)); + out_row[x * 3 + 1] = htons(static_cast(row[x].g)); + out_row[x * 3 + 2] = htons(static_cast(row[x].b)); + } + png_write_row(png_ptr, reinterpret_cast(out_row.data())); + } + png_write_end(png_ptr, nullptr); + + png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); + png_destroy_write_struct(&png_ptr, &info_ptr); + + return ret; +} + +template +Image* PiRaw::GetImage() { + return image_.get(); +} + +template +const Image& PiRaw::GetImage() const { + return *image_; +} diff --git a/util.cc b/util.cc new file mode 100644 index 0000000..e53f348 --- /dev/null +++ b/util.cc @@ -0,0 +1,31 @@ +#include "util.h" + +#include +#include +#include +#include + +#include + +std::string ReadFile(const std::string& filename) { + int fh = open(filename.c_str(), O_RDONLY); + assert(fh != -1); + + struct stat st; + assert(fstat(fh, &st) == 0); + + std::string contents; + contents.resize(static_cast(st.st_size)); + + assert(read(fh, &contents[0], static_cast(st.st_size)) == st.st_size); + assert(close(fh) == 0); + + return contents; +} + +void WriteFile(const std::string& filename, const std::string& contents) { + int fh = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); + assert(fh != -1); + assert(write(fh, &contents[0], contents.size()) == static_cast(contents.size())); + assert(close(fh) == 0); +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..06b02f9 --- /dev/null +++ b/util.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +std::string ReadFile(const std::string& filename); +void WriteFile(const std::string& filename, const std::string& contents);