diff --git a/NOTICE b/NOTICE index 8022063..d6b6a9f 100644 --- a/NOTICE +++ b/NOTICE @@ -24,3 +24,31 @@ Notice file for preview/fonts/FreeSans*.cpp CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Notice file for preview/interfaces/QrCode.cpp/hpp + + The MIT License (MIT) + + This library is written and maintained by Richard Moore. + Major parts were derived from Project Nayuki's library. + + Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode) + Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 52bce91..541a0fc 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(clicking_scrolling) +add_subdirectory(qr_code) diff --git a/examples/qr_code/CMakeLists.txt b/examples/qr_code/CMakeLists.txt new file mode 100644 index 0000000..a55fdd3 --- /dev/null +++ b/examples/qr_code/CMakeLists.txt @@ -0,0 +1,33 @@ +if (EMIL_BUILD_WIN OR TARGET_MCU STREQUAL stm32f746) + add_executable(examples.qr_code) + emil_build_for(examples.qr_code HOST Windows TARGET_MCU stm32f746 PREREQUISITE_BOOL PREVIEW_BUILD_EXAMPLES) + + set_target_properties(examples.qr_code PROPERTIES WIN32_EXECUTABLE $) + if (EMIL_BUILD_WIN) + target_compile_definitions(examples.qr_code PUBLIC NOMINMAX) + endif() + + target_include_directories(examples.qr_code PUBLIC + "$" + "$" + ) + + target_sources(examples.qr_code PRIVATE + $<$:MainWin.cpp> + $<$:MainStm32f746.cpp> + ) + + target_link_libraries(examples.qr_code PUBLIC + $<$:hal.generic> + $<$:preview.sdl> + $<$:hal_st.stm32fxxx> + $<$:preview.stm32fxxx> + preview.touch + preview.views + ) + + if(TARGET_MCU STREQUAL stm32f746) + halst_target_default_linker_scripts(examples.qr_code) + halst_target_default_init(examples.qr_code) + endif() +endif() diff --git a/examples/qr_code/MainStm32f746.cpp b/examples/qr_code/MainStm32f746.cpp new file mode 100644 index 0000000..8602992 --- /dev/null +++ b/examples/qr_code/MainStm32f746.cpp @@ -0,0 +1,59 @@ +#include "generated/stm32fxxx/PinoutTableDefault.hpp" +#include "hal_st/stm32fxxx/DefaultClockDiscoveryF746G.hpp" +#include "hal_st/stm32fxxx/GpioStm.hpp" +#include "hal_st/stm32fxxx/I2cStm.hpp" +#include "hal_st/stm32fxxx/SdRamStm.hpp" +#include "hal_st/stm32fxxx/SystemTickTimerService.hpp" +#include "infra/event/EventDispatcherWithWeakPtr.hpp" +#include "preview/interfaces/QrCode.hpp" +#include "preview/interfaces/ViewPainterDoubleBufferDisplay.hpp" +#include "preview/interfaces/ViewRepainter.hpp" +#include "preview/stm32fxxx/BitmapPainterStm.hpp" +#include "preview/stm32fxxx/LcdStm.hpp" +#include "preview/views/ViewBitmap.hpp" +#include "services/util/DebugLed.hpp" + +unsigned int hse_value = 25000000; + +namespace main_ +{ + struct Lcd + { + hal::MultiGpioPinStm sdRamPins{ hal::stm32f7discoveryFmcPins }; + hal::SdRamStm sdRam{ sdRamPins, hal::stm32f7discoverySdRamConfig }; + + hal::MultiGpioPinStm lcdPins{ hal::stm32f7discoveryLcdPins }; + hal::GpioPinStm displayEnable{ hal::Port::I, 12 }; + hal::GpioPinStm backlightEnable{ hal::Port::K, 3 }; + uint32_t bufferSize{ infra::Bitmap::BufferSize(hal::stm32f7discoveryLcdConfig.width, hal::stm32f7discoveryLcdConfig.height, hal::stm32f7discoveryLcdConfig.pixelFormat) }; + infra::ByteRange lcdBuffer0{ infra::Head(sdRam.Memory(), bufferSize) }; + infra::ByteRange lcdBuffer1{ infra::Head(infra::DiscardHead(sdRam.Memory(), bufferSize), bufferSize) }; + infra::Bitmap bitmap0{ lcdBuffer0, infra::Vector(480, 272), infra::PixelFormat::rgb565 }; + infra::Bitmap bitmap1{ lcdBuffer1, infra::Vector(480, 272), infra::PixelFormat::rgb565 }; + hal::LcdStmDoubleBuffer display{ lcdPins, displayEnable, backlightEnable, lcdBuffer0, lcdBuffer1, hal::stm32f7discoveryLcdConfig }; + }; +} + +int main() +{ + HAL_Init(); + ConfigureDefaultClockDiscoveryF746G(); + + static hal::InterruptTable::WithStorage<128> interruptTable; + static infra::EventDispatcherWithWeakPtr::WithSize<50> eventDispatcher; + static hal::GpioStm gpio(hal::pinoutTableDefaultStm); + static hal::SystemTickTimerService systemTick; + + static main_::Lcd lcd; + static hal::BitmapPainterStm bitmapPainter; + static services::ViewPainterDoubleBufferDisplay painter(lcd.display, bitmapPainter); + + services::QrCode<3, services::QrCodeEcc::low> qrcode("https://github.com/philips-software/amp-preview"); + services::ViewBitmap viewBitmap(qrcode); + + static services::ViewRepainterPaintWhenDirty repainter(painter, viewBitmap); + viewBitmap.ResetLayout(lcd.display.DisplaySize()); + + eventDispatcher.Run(); + __builtin_unreachable(); +} diff --git a/examples/qr_code/MainWin.cpp b/examples/qr_code/MainWin.cpp new file mode 100644 index 0000000..9b38ccb --- /dev/null +++ b/examples/qr_code/MainWin.cpp @@ -0,0 +1,30 @@ +#include "hal/generic/TimerServiceGeneric.hpp" +#include "infra/event/LowPowerEventDispatcher.hpp" +#include "preview/interfaces/QrCode.hpp" +#include "preview/interfaces/ViewPainterDirectDisplay.hpp" +#include "preview/interfaces/ViewRepainter.hpp" +#include "preview/sdl/DirectDisplaySdl.hpp" +#include "preview/sdl/LowPowerStrategySdl.hpp" +#include "preview/sdl/SdlTouchInteractor.hpp" +#include "preview/views/ViewBitmap.hpp" +#include + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) +{ + hal::TimerServiceGeneric timerService; + services::LowPowerStrategySdl lowPowerStrategy(timerService); + infra::LowPowerEventDispatcher::WithSize<50> eventDispatcher(lowPowerStrategy); + + services::QrCode<4, services::QrCodeEcc::low> qrcode("https://github.com/philips-software/amp-preview/pull/191"); + services::ViewBitmap viewBitmap(qrcode); + + hal::DirectDisplaySdl display(infra::Vector(480, 272)); + services::ViewPainterDirectDisplay painter(display); + + services::ViewRepainterPaintWhenDirty repainter(painter, viewBitmap); + viewBitmap.ResetLayout(display.Size()); + + eventDispatcher.Run(); + + return 0; +} diff --git a/preview/interfaces/Bitmap.cpp b/preview/interfaces/Bitmap.cpp index ce62de8..d139de0 100644 --- a/preview/interfaces/Bitmap.cpp +++ b/preview/interfaces/Bitmap.cpp @@ -13,6 +13,11 @@ namespace infra assert(buffer.size() == BufferSize(size.deltaX, size.deltaY, pixelFormat)); } + void Bitmap::Clear() + { + std::fill(buffer.begin(), buffer.end(), 0); + } + const uint8_t* Bitmap::BufferAddress(infra::Point position) const { assert(pixelFormat != PixelFormat::blackandwhite); @@ -40,7 +45,7 @@ namespace infra assert(pixelFormat == PixelFormat::blackandwhite); auto bitIndex = position.y * size.deltaX + position.x; - return (buffer[bitIndex / 8] & (1 << (7 - bitIndex % 8))) != 0; + return (buffer[bitIndex / 8] & (1 << (bitIndex % 8))) != 0; } uint32_t Bitmap::PixelColour(infra::Point position) const diff --git a/preview/interfaces/Bitmap.hpp b/preview/interfaces/Bitmap.hpp index cc77e59..350ae14 100644 --- a/preview/interfaces/Bitmap.hpp +++ b/preview/interfaces/Bitmap.hpp @@ -20,6 +20,8 @@ namespace infra Bitmap(infra::ByteRange buffer, infra::Vector size, PixelFormat pixelFormat); + void Clear(); + const uint8_t* BufferAddress(infra::Point position) const; uint8_t* BufferAddress(infra::Point position); void SetBlackAndWhitePixel(infra::Point position, bool pixel); diff --git a/preview/interfaces/CMakeLists.txt b/preview/interfaces/CMakeLists.txt index 6c22cc9..097a326 100644 --- a/preview/interfaces/CMakeLists.txt +++ b/preview/interfaces/CMakeLists.txt @@ -35,6 +35,8 @@ target_sources(preview.interfaces PRIVATE Geometry.cpp Geometry.hpp MultiBufferDisplay.hpp + QrCode.cpp + QrCode.hpp View.cpp View.hpp ViewOverlay.cpp diff --git a/preview/interfaces/Geometry.cpp b/preview/interfaces/Geometry.cpp index 4c88fa2..c0e7d4a 100644 --- a/preview/interfaces/Geometry.cpp +++ b/preview/interfaces/Geometry.cpp @@ -522,6 +522,59 @@ namespace infra return !(*this == other); } + RowFirstPoints::RowFirstPoints(infra::Region region) + : region(region) + , current(region.TopLeft()) + {} + + RowFirstPoints RowFirstPoints::begin() const + { + return *this; + } + + RowFirstPoints RowFirstPoints::end() const + { + RowFirstPoints result(*this); + result.current = region.BottomRight(); + result.current.x = region.Left(); + return result; + } + + Point RowFirstPoints::operator*() const + { + return current; + } + + RowFirstPoints& RowFirstPoints::operator++() + { + ++current.x; + + if (current.x == region.Right()) + { + current.x = region.Left(); + ++current.y; + } + + return *this; + } + + RowFirstPoints RowFirstPoints::operator++(int) const + { + RowFirstPoints result(*this); + ++result; + return result; + } + + bool RowFirstPoints::operator==(const RowFirstPoints& other) const + { + return region == other.region && current == other.current; + } + + bool RowFirstPoints::operator!=(const RowFirstPoints& other) const + { + return !(*this == other); + } + Region Intersection(Region first, Region second) { if (first.Top() >= second.Bottom() || first.Bottom() <= second.Top() || first.Left() >= second.Right() || first.Right() <= second.Left()) @@ -583,6 +636,11 @@ namespace infra return std::abs(first.x - second.x) + std::abs(first.y - second.y); } + uint32_t ChebyshevDistance(Point first, Point second) + { + return std::max(std::abs(first.x - second.x), std::abs(first.y - second.y)); + } + uint32_t Distance(Point first, Point second) { return SquareRoot(Square(first.x - second.x) + Square(first.y - second.y)); diff --git a/preview/interfaces/Geometry.hpp b/preview/interfaces/Geometry.hpp index df194db..10ba3e2 100644 --- a/preview/interfaces/Geometry.hpp +++ b/preview/interfaces/Geometry.hpp @@ -176,6 +176,26 @@ namespace infra Vector size; }; + class RowFirstPoints + { + public: + explicit RowFirstPoints(infra::Region region); + + RowFirstPoints begin() const; + RowFirstPoints end() const; + + Point operator*() const; + RowFirstPoints& operator++(); + RowFirstPoints operator++(int) const; + + bool operator==(const RowFirstPoints& other) const; + bool operator!=(const RowFirstPoints& other) const; + + private: + infra::Region region; + infra::Point current; + }; + Region Intersection(Region first, Region second); Region Union(Region first, Region second); Region operator&(Region first, Region second); @@ -186,6 +206,7 @@ namespace infra Vector Flip(Vector vector); Region Flip(Region region); uint32_t ManhattanDistance(Point first, Point second); + uint32_t ChebyshevDistance(Point first, Point second); uint32_t Distance(Point first, Point second); Point AlignedUp(Point point, uint16_t alignX, uint16_t alignY); Point AlignedDown(Point point, uint16_t alignX, uint16_t alignY); diff --git a/preview/interfaces/QrCode.cpp b/preview/interfaces/QrCode.cpp new file mode 100644 index 0000000..88bd7e6 --- /dev/null +++ b/preview/interfaces/QrCode.cpp @@ -0,0 +1,686 @@ +#include "preview/interfaces/QrCode.hpp" +#include +#include +#include + +namespace services +{ + namespace detail + { + TextEncoder::TextEncoder(infra::ByteRange data, infra::ByteRange result, infra::ByteRange coeff, uint8_t version, QrCodeEcc ecc) + : version(version) + , ecc(ecc) + , moduleCount(numRawDataModulesForVersion[version]) + , data(data) + , result(result) + , reedSolomon(coeff) + {} + + void TextEncoder::Encode(infra::BoundedConstString text) + { + uint16_t dataCapacity = moduleCount / 8 - numErrorCorrectionCodewords[static_cast(ecc)][version - 1]; + + EncodeDataCodewords(text); + + // Add terminator and pad up to a byte if applicable + uint32_t padding = std::min((dataCapacity * 8) - bitOffset, 4); + + Append(0, padding); + Append(0, (8 - bitOffset % 8) % 8); + + // Pad with alternate bytes until data capacity is reached + for (uint8_t padByte = 0xEC; bitOffset != dataCapacity * 8; padByte ^= 0xEC ^ 0x11) + Append(padByte, 8); + + PerformErrorCorrection(ecc); + } + + uint16_t TextEncoder::Length() const + { + return bitOffset; + } + + bool TextEncoder::Bit(uint16_t index) const + { + return ((result[index >> 3] >> (7 - (index & 7))) & 1) != 0; + } + + void TextEncoder::Append(uint32_t val, uint8_t length) + { + for (int8_t i = length - 1; i >= 0; --i, ++bitOffset) + data[bitOffset >> 3] |= ((val >> i) & 1) << (7 - (bitOffset & 7)); + } + + void TextEncoder::EncodeDataCodewords(infra::BoundedConstString text) + { + if (IsNumeric(text)) + EncodeNumeric(text); + else if (IsAlphanumeric(text)) + EncodeAlphanumeric(text); + else + EncodeLatin1(text); + } + + void TextEncoder::EncodeNumeric(infra::BoundedConstString text) + { + assert(text.size() <= MaxSizeNumeric(version, ecc)); + Append(1 << numeric, 4); + Append(text.size(), ModeBitsNumeric(version)); + + uint16_t accumData = 0; + uint8_t accumCount = 0; + for (auto c : text) + { + accumData = accumData * 10 + (c - '0'); + ++accumCount; + if (accumCount == 3) + { + Append(accumData, 10); + accumData = 0; + accumCount = 0; + } + } + + // 1 or 2 digits remaining + if (accumCount > 0) + Append(accumData, accumCount * 3 + 1); + } + + void TextEncoder::EncodeAlphanumeric(infra::BoundedConstString text) + { + assert(text.size() <= MaxSizeAlphanumeric(version, ecc)); + Append(1 << alphanumeric, 4); + Append(text.size(), ModeBitsAlphanumeric(version)); + + uint16_t accumData = 0; + uint8_t accumCount = 0; + for (auto c : text) + { + accumData = accumData * 45 + GetAlphanumeric(c); + ++accumCount; + if (accumCount == 2) + { + Append(accumData, 11); + accumData = 0; + accumCount = 0; + } + } + + if (accumCount == 1) + Append(accumData, 6); + } + + void TextEncoder::EncodeLatin1(infra::BoundedConstString text) + { + assert(text.size() <= MaxSizeLatin1(version, ecc)); + Append(1 << latin1, 4); + Append(text.size(), ModeBitsLatin1(version)); + for (auto c : text) + Append(c, 8); + } + + void TextEncoder::PerformErrorCorrection(QrCodeEcc ecc) + { + // See: http://www.thonky.com/qr-code-tutorial/structure-final-message + uint8_t numBlocks = numErrorCorrectionBlocks[static_cast(ecc)][version - 1]; + uint16_t totalEcc = numErrorCorrectionCodewords[static_cast(ecc)][version - 1]; + + uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks; + uint8_t shortBlockLen = moduleCount / 8 / numBlocks; + + uint8_t shortDataBlockLen = shortBlockLen - BlockEccLen(version, ecc); + + uint16_t offset = 0; + uint8_t* dataBytes = data.begin(); + + // Interleave all short blocks + for (uint8_t i = 0; i != shortDataBlockLen; ++i) + { + uint16_t index = i; + uint8_t stride = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum != numBlocks; ++blockNum) + { + result[offset++] = dataBytes[index]; + + if (blockNum == numShortBlocks) + ++stride; + + index += stride; + } + } + + // Interleave long blocks (versions less than 5 only have short blocks) + uint16_t index = shortDataBlockLen * (numShortBlocks + 1); + uint8_t stride = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum != numBlocks - numShortBlocks; ++blockNum) + { + result[offset++] = dataBytes[index]; + + if (blockNum == 0) + ++stride; + + index += stride; + } + + // Add all ecc blocks, interleaved + uint8_t blockSize = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum != numBlocks; ++blockNum) + { + + if (blockNum == numShortBlocks) + ++blockSize; + + reedSolomon.Remainder(dataBytes, blockSize, &result[offset + blockNum], numBlocks); + dataBytes += blockSize; + } + + bitOffset = moduleCount; + } + + int8_t TextEncoder::GetAlphanumeric(char c) + { + if (c >= '0' && c <= '9') + return (c - '0'); + if (c >= 'A' && c <= 'Z') + return (c - 'A' + 10); + + switch (c) + { + case ' ': + return 36; + case '$': + return 37; + case '%': + return 38; + case '*': + return 39; + case '+': + return 40; + case '-': + return 41; + case '.': + return 42; + case '/': + return 43; + case ':': + return 44; + default: + return -1; + } + } + + bool TextEncoder::IsAlphanumeric(infra::BoundedConstString text) + { + for (auto c : text) + if (GetAlphanumeric(c) == -1) + return false; + + return true; + } + + bool TextEncoder::IsNumeric(infra::BoundedConstString text) + { + for (auto c : text) + if (c < '0' || c > '9') + return false; + + return true; + } + + TextEncoder::ReedSolomon::ReedSolomon(infra::ByteRange coeff) + : coeff(coeff) + { + coeff.back() = 1; + + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{coeff.size()-1}), + // drop the highest term, and store the rest of the coefficients in order of descending powers. + // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). + uint16_t root = 1; + for (uint8_t i = 0; i != coeff.size(); ++i) + { + // Multiply the current product by (x - r^i) + for (uint8_t j = 0; j != coeff.size(); ++j) + { + coeff[j] = Multiply(coeff[j], root); + if (j + 1 < coeff.size()) + coeff[j] ^= coeff[j + 1]; + } + root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D) + } + } + + void TextEncoder::ReedSolomon::Remainder(uint8_t* data, uint8_t length, uint8_t* result, uint8_t stride) const + { + // Compute the remainder by performing polynomial division + for (uint8_t i = 0; i != length; ++i) + { + uint8_t factor = data[i] ^ result[0]; + for (uint8_t j = 1; j != coeff.size(); ++j) + result[(j - 1) * stride] = result[j * stride]; + + result[(coeff.size() - 1) * stride] = 0; + + for (uint8_t j = 0; j != coeff.size(); ++j) + result[j * stride] ^= Multiply(coeff[j], factor); + } + } + + uint8_t TextEncoder::ReedSolomon::Multiply(uint8_t x, uint8_t y) const + { + // Russian peasant multiplication + // See: https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication + uint16_t z = 0; + for (int8_t i = 7; i >= 0; i--) + { + z = (z << 1) ^ ((z >> 7) * 0x11D); + z ^= ((y >> i) & 1) * x; + } + return z; + } + + uint32_t PenaltyScoreRowRuns(const infra::Bitmap& bitmap) + { + uint32_t penalty = 0; + + for (uint8_t y = 0; y != bitmap.size.deltaY; ++y) + { + bool runColour = bitmap.BlackAndWhitePixel(infra::Point(0, y)); + uint8_t run = 1; + for (uint8_t x = 1; x != bitmap.size.deltaX; ++x) + { + bool colour = bitmap.BlackAndWhitePixel(infra::Point(x, y)); + if (colour != runColour) + { + runColour = colour; + run = 1; + } + else + { + ++run; + if (run == 5) + penalty += 3; + else if (run > 5) + ++penalty; + } + } + } + + return penalty; + } + + uint32_t PenaltyScoreColumnRuns(const infra::Bitmap& bitmap) + { + uint32_t penalty = 0; + + for (uint8_t x = 0; x != bitmap.size.deltaX; ++x) + { + bool runColour = bitmap.BlackAndWhitePixel(infra::Point(x, 0)); + uint8_t run = 1; + for (uint8_t y = 1; y != bitmap.size.deltaY; ++y) + { + bool colour = bitmap.BlackAndWhitePixel(infra::Point(x, y)); + if (colour != runColour) + { + runColour = colour; + run = 1; + } + else + { + ++run; + if (run == 5) + penalty += 3; + else if (run > 5) + ++penalty; + } + } + } + + return penalty; + } + + uint32_t PenaltyScoreBlocks(const infra::Bitmap& bitmap) + { + uint32_t penalty = 0; + + for (uint8_t y = 0; y != bitmap.size.deltaY; ++y) + for (uint8_t x = 0; x != bitmap.size.deltaX; ++x) + { + bool color = bitmap.BlackAndWhitePixel(infra::Point(x, y)); + + // 2*2 blocks of modules having same color + if (x > 0 && y > 0) + { + bool colorUL = bitmap.BlackAndWhitePixel(infra::Point(x - 1, y - 1)); + bool colorUR = bitmap.BlackAndWhitePixel(infra::Point(x, y - 1)); + bool colorL = bitmap.BlackAndWhitePixel(infra::Point(x - 1, y)); + if (color == colorUL && color == colorUR && color == colorL) + penalty += 3; + } + } + + return penalty; + } + + uint32_t PenaltyScoreFinderLike(const infra::Bitmap& bitmap) + { + uint32_t penalty = 0; + + for (uint8_t y = 0; y != bitmap.size.deltaY; ++y) + { + uint16_t bitsRow = 0; + uint16_t bitsCol = 0; + for (uint8_t x = 0; x != bitmap.size.deltaX; ++x) + { + // Finder-like pattern in rows and columns + bitsRow = ((bitsRow << 1) & 0x7FF) | static_cast(bitmap.BlackAndWhitePixel(infra::Point(x, y))); + bitsCol = ((bitsCol << 1) & 0x7FF) | static_cast(bitmap.BlackAndWhitePixel(infra::Point(y, x))); + + // Needs 11 bits accumulated + if (x >= 10) + { + if (bitsRow == 0x05D || bitsRow == 0x5D0) + penalty += 40; + + if (bitsCol == 0x05D || bitsCol == 0x5D0) + penalty += 40; + } + } + } + + return penalty; + } + + uint32_t PenaltyScoreBalance(const infra::Bitmap& bitmap) + { + uint32_t penalty = 0; + + uint16_t numDark = 0; + for (uint8_t y = 0; y != bitmap.size.deltaY; ++y) + for (uint8_t x = 0; x != bitmap.size.deltaX; ++x) + if (bitmap.BlackAndWhitePixel(infra::Point(x, y))) + ++numDark; + + // Find smallest k such that (45-5k)% <= dark/total <= (55+5k)% + uint16_t total = bitmap.size.deltaX * bitmap.size.deltaY; + for (uint16_t k = 0; numDark * 20 < (9 - k) * total || numDark * 20 > (11 + k) * total; ++k) + penalty += 10; + + return penalty; + } + + // Calculates and returns the penalty score based on the state of this QR Code's current modules. + // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. + uint32_t PenaltyScore(const infra::Bitmap& bitmap) + { + return PenaltyScoreRowRuns(bitmap) + PenaltyScoreColumnRuns(bitmap) + PenaltyScoreBlocks(bitmap) + PenaltyScoreFinderLike(bitmap) + PenaltyScoreBalance(bitmap); + } + + QrCodeGenerator::QrCodeGenerator(infra::Bitmap& modules, infra::Bitmap& isFunction, TextEncoder& encoder, infra::ByteRange alignPosition, uint8_t version, QrCodeEcc ecc) + : version(version) + , ecc(ecc) + , modules(modules) + , isFunction(isFunction) + , encoder(encoder) + , alignPosition(alignPosition) + {} + + void QrCodeGenerator::Generate(infra::BoundedConstString text) + { + encoder.Encode(text); + + DrawFunctionPatterns(); + DrawCodewords(); + + uint8_t mask = BestMask(); + DrawFormatBits(mask); + ApplyMask(mask); + } + + uint8_t QrCodeGenerator::BestMask() + { + uint8_t mask = 0; + + int32_t minPenalty = std::numeric_limits::max(); + for (uint8_t i = 0; i != 8; ++i) + { + DrawFormatBits(i); + ApplyMask(i); + int penalty = PenaltyScore(modules); + if (penalty < minPenalty) + { + mask = i; + minPenalty = penalty; + } + ApplyMask(i); // Removes the mask due to XOR + } + + return mask; + } + + // XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical + // properties, calling ApplyMask(m) twice with the same value is equivalent to no change at all. + // This means it is possible to apply a mask, undo it, and try another mask. Note that a final + // well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). + void QrCodeGenerator::ApplyMask(uint8_t mask) + { + uint8_t size = modules.size.deltaX; + + for (auto position : infra::RowFirstPoints(infra::Region(infra::Point(), modules.size))) + { + if (isFunction.BlackAndWhitePixel(position)) + continue; + + bool invert = 0; + switch (mask) + { + case 0: + invert = (position.x + position.y) % 2 == 0; + break; + case 1: + invert = position.y % 2 == 0; + break; + case 2: + invert = position.x % 3 == 0; + break; + case 3: + invert = (position.x + position.y) % 3 == 0; + break; + case 4: + invert = (position.x / 3 + position.y / 2) % 2 == 0; + break; + case 5: + invert = position.x * position.y % 2 + position.x * position.y % 3 == 0; + break; + case 6: + invert = (position.x * position.y % 2 + position.x * position.y % 3) % 2 == 0; + break; + case 7: + invert = ((position.x + position.y) % 2 + position.x * position.y % 3) % 2 == 0; + break; + } + + if (invert) + modules.SetBlackAndWhitePixel(position, !modules.BlackAndWhitePixel(position)); + } + } + + void QrCodeGenerator::DrawFunctionPatterns() + { + uint8_t size = modules.size.deltaX; + + // Draw the horizontal and vertical timing patterns + for (uint8_t i = 0; i != size; ++i) + { + SetFunctionModule(infra::Point(6, i), i % 2 == 0); + SetFunctionModule(infra::Point(i, 6), i % 2 == 0); + } + + // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) + DrawFinderPattern(infra::Point(3, 3)); + DrawFinderPattern(infra::Point(size - 4, 3)); + DrawFinderPattern(infra::Point(3, size - 4)); + + if (version > 1) + { + // Draw the numerous alignment patterns + + uint8_t step; + if (version != 32) + step = (version * 4 + alignPosition.size() * 2 + 1) / (2 * alignPosition.size() - 2) * 2; + else + step = 26; + + uint8_t alignPositionIndex = alignPosition.size() - 1; + + alignPosition[0] = 6; + + uint8_t size = version * 4 + 17; + for (uint8_t i = 0, pos = size - 7; i != alignPosition.size() - 1; ++i, pos -= step) + alignPosition[alignPositionIndex--] = pos; + + for (uint8_t i = 0; i != alignPosition.size(); ++i) + for (uint8_t j = 0; j != alignPosition.size(); ++j) + // Skip the three finder corners + if (!(i == 0 && j == 0) && !(i == 0 && j == alignPosition.size() - 1) && !(i == alignPosition.size() - 1 && j == 0)) + DrawAlignmentPattern(infra::Point(alignPosition[i], alignPosition[j])); + } + + // Draw configuration data + DrawFormatBits(0); // Dummy mask value; overwritten later in the constructor + DrawVersion(); + } + + // Draws two copies of the version bits (with its own error correction code), + // based on this object's version field (which only has an effect for 7 <= version <= 40). + void QrCodeGenerator::DrawVersion() + { + int8_t size = modules.size.deltaX; + + if (version < 7) + return; + + // Calculate error correction code and pack bits + uint32_t rem = version; // version is uint6, in the range [7, 40] + for (uint8_t i = 0; i != 12; ++i) + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); + + uint32_t data = version << 12 | rem; + + // Draw two copies + for (uint8_t i = 0; i != 18; ++i) + { + bool bit = ((data >> i) & 1) != 0; + uint8_t a = size - 11 + i % 3; + uint8_t b = i / 3; + SetFunctionModule(infra::Point(a, b), bit); + SetFunctionModule(infra::Point(b, a), bit); + } + } + + // Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). + void QrCodeGenerator::DrawFinderPattern(infra::Point position) + { + auto bitmapRegion = infra::Region(infra::Point(), modules.size); + auto finderRegion = infra::Region(infra::Point(-4, -4), infra::Point(5, 5)) >> (position - infra::Point()); + + for (auto i : infra::RowFirstPoints(infra::Intersection(finderRegion, bitmapRegion))) + { + auto dist = infra::ChebyshevDistance(i, position); + SetFunctionModule(i, dist != 2 && dist != 4); + } + } + + // Draws a 5*5 alignment pattern, with the center module at (x, y). + void QrCodeGenerator::DrawAlignmentPattern(infra::Point position) + { + auto alignmentRegion = infra::Region(infra::Point(-2, -2), infra::Point(3, 3)) >> (position - infra::Point()); + + for (auto i : infra::RowFirstPoints(alignmentRegion)) + SetFunctionModule(i, infra::ChebyshevDistance(i, position) != 1); + } + + // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire + // data area of this QR Code symbol. Function modules need to be marked off before this is called. + void QrCodeGenerator::DrawCodewords() + { + uint8_t size = modules.size.deltaX; + + // Bit index into the data + uint32_t i = 0; + + // Do the funny zigzag scan + for (int16_t right = size - 1; right >= 1; right -= 2) + { // Index of right column in each column pair + if (right == 6) + right = 5; + + for (uint8_t vert = 0; vert != size; ++vert) + { // Vertical counter + for (int j = 0; j != 2; ++j) + { + uint8_t x = right - j; // Actual x coordinate + bool upwards = ((right & 2) == 0) ^ (x < 6); + uint8_t y = upwards ? size - 1 - vert : vert; // Actual y coordinate + auto position = infra::Point(x, y); + if (!isFunction.BlackAndWhitePixel(position)) + { + modules.SetBlackAndWhitePixel(position, encoder.Bit(i)); + ++i; + + if (i == encoder.Length()) + return; + } + // If there are any remainder bits (0 to 7), they are already + // set to 0/false/white when the grid of modules was initialized + } + } + } + } + + // Draws two copies of the format bits (with its own error correction code) + // based on the given mask and this object's error correction level field. + void QrCodeGenerator::DrawFormatBits(uint8_t mask) + { + static constexpr std::array eccFormatBits{ + 0x01, 0x00, 0x03, 0x02 + }; + + uint8_t size = modules.size.deltaX; + + // Calculate error correction code and pack bits + uint32_t data = eccFormatBits[static_cast(ecc)] << 3 | mask; // ecc is uint2, mask is uint3 + uint32_t rem = data; + for (int i = 0; i != 10; ++i) + rem = (rem << 1) ^ ((rem >> 9) * 0x537); + + data = data << 10 | rem; + data ^= 0x5412; // uint15 + + // Draw first copy + for (uint8_t i = 0; i != 6; ++i) + SetFunctionModule(infra::Point(8, i), ((data >> i) & 1) != 0); + + SetFunctionModule(infra::Point(8, 7), ((data >> 6) & 1) != 0); + SetFunctionModule(infra::Point(8, 8), ((data >> 7) & 1) != 0); + SetFunctionModule(infra::Point(7, 8), ((data >> 8) & 1) != 0); + + for (int8_t i = 9; i != 15; ++i) + SetFunctionModule(infra::Point(14 - i, 8), ((data >> i) & 1) != 0); + + // Draw second copy + for (int8_t i = 0; i != 8; ++i) + SetFunctionModule(infra::Point(size - 1 - i, 8), ((data >> i) & 1) != 0); + + for (int8_t i = 8; i != 15; ++i) + SetFunctionModule(infra::Point(8, size - 15 + i), ((data >> i) & 1) != 0); + + SetFunctionModule(infra::Point(8, size - 8), true); + } + + void QrCodeGenerator::SetFunctionModule(infra::Point position, bool on) + { + modules.SetBlackAndWhitePixel(position, on); + isFunction.SetBlackAndWhitePixel(position, true); + } + } +} diff --git a/preview/interfaces/QrCode.hpp b/preview/interfaces/QrCode.hpp new file mode 100644 index 0000000..31edfe2 --- /dev/null +++ b/preview/interfaces/QrCode.hpp @@ -0,0 +1,307 @@ +#ifndef PREVIEW_QR_CODE_HPP +#define PREVIEW_QR_CODE_HPP + +#include "infra/util/BoundedString.hpp" +#include "infra/util/ByteRange.hpp" +#include "preview/interfaces/Bitmap.hpp" +#include +#include + +namespace services +{ + enum class QrCodeEcc : uint8_t + { + low, + medium, + quartile, + high + }; + + template + struct QrCode + : infra::Bitmap::BlackAndWhite + { + QrCode(infra::BoundedConstString text); + + void Update(infra::BoundedConstString text); + + static constexpr std::size_t MaxSizeNumeric(); + static constexpr std::size_t MaxSizeAlphanumeric(); + static constexpr std::size_t MaxSizeLatin1(); + + using BitmapType = infra::Bitmap::BlackAndWhite; + }; + + namespace detail + { + class TextEncoder + { + private: + static constexpr std::array numRawDataModulesForVersion{ + 0, 208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523, + 7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587, + 19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648 + }; + + static constexpr auto numErrorCorrectionCodewords = []() + { + std::array, 4> result{ { + // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + { 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750 }, // Low + { 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372 }, // Medium + { 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040 }, // Quartile + { 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430 }, // High + } }; + + return result; + }(); + + static constexpr auto numErrorCorrectionBlocks = []() + { + const std::array, 4> result{ { + { 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25 }, // Low + { 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49 }, // Medium + { 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68 }, // Quartile + { 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81 }, // High + } }; + + return result; + }(); + + static constexpr uint16_t RoundBitsToByte(uint16_t bits) + { + return (bits + 7) / 8; + } + + static constexpr uint8_t BlockEccLen(uint8_t version, QrCodeEcc ecc) + { + uint8_t numBlocks = numErrorCorrectionBlocks[static_cast(ecc)][version - 1]; + uint16_t totalEcc = numErrorCorrectionCodewords[static_cast(ecc)][version - 1]; + return totalEcc / numBlocks; + } + + static constexpr uint8_t ModeBitsNumeric(uint8_t version) + { + if (version <= 9) + return 10; + if (version <= 26) + return 12; + return 14; + } + + static constexpr uint8_t ModeBitsAlphanumeric(uint8_t version) + { + if (version <= 9) + return 9; + if (version <= 26) + return 11; + return 13; + } + + static constexpr uint8_t ModeBitsLatin1(uint8_t version) + { + if (version <= 9) + return 8; + if (version <= 26) + return 16; + return 16; + } + + public: + static constexpr std::size_t MaxSizeNumeric(uint8_t version, QrCodeEcc ecc) + { + uint16_t moduleCount = numRawDataModulesForVersion[version]; + uint16_t dataCapacityInBytes = moduleCount / 8 - numErrorCorrectionCodewords[static_cast(ecc)][version - 1]; + uint16_t digitBits = dataCapacityInBytes * 8 - 4 - ModeBitsNumeric(version); + + uint16_t blocksOf3 = digitBits / 10; + digitBits -= blocksOf3 * 10; + + return blocksOf3 * 3 + (digitBits >= 7 ? 2 : digitBits >= 4 ? 1 + : 0); + } + + static constexpr std::size_t MaxSizeAlphanumeric(uint8_t version, QrCodeEcc ecc) + { + uint16_t moduleCount = numRawDataModulesForVersion[version]; + uint16_t dataCapacityInBytes = moduleCount / 8 - numErrorCorrectionCodewords[static_cast(ecc)][version - 1]; + uint16_t digitBits = dataCapacityInBytes * 8 - 4 - ModeBitsAlphanumeric(version); + + uint16_t blocksOf2 = digitBits / 11; + digitBits -= blocksOf2 * 11; + + return blocksOf2 * 2 + digitBits / 6; + } + + static constexpr std::size_t MaxSizeLatin1(uint8_t version, QrCodeEcc ecc) + { + uint16_t moduleCount = numRawDataModulesForVersion[version]; + uint16_t dataCapacityInBytes = moduleCount / 8 - numErrorCorrectionCodewords[static_cast(ecc)][version - 1]; + uint16_t digitBits = dataCapacityInBytes * 8 - 4 - ModeBitsLatin1(version); + + return digitBits / 8; + } + + public: + template + struct ForVersionAndEcc; + + TextEncoder(infra::ByteRange data, infra::ByteRange result, infra::ByteRange coeff, uint8_t version, QrCodeEcc ecc); + + void Encode(infra::BoundedConstString text); + + uint16_t Length() const; + bool Bit(uint16_t index) const; + + private: + static constexpr uint8_t numeric = 0; + static constexpr uint8_t alphanumeric = 1; + static constexpr uint8_t latin1 = 2; + + private: + void Append(uint32_t val, uint8_t length); + void EncodeDataCodewords(infra::BoundedConstString text); + void EncodeNumeric(infra::BoundedConstString text); + void EncodeAlphanumeric(infra::BoundedConstString text); + void EncodeLatin1(infra::BoundedConstString text); + void PerformErrorCorrection(QrCodeEcc ecc); + + static int8_t GetAlphanumeric(char c); + static bool IsAlphanumeric(infra::BoundedConstString text); + static bool IsNumeric(infra::BoundedConstString text); + + private: + class ReedSolomon + { + public: + ReedSolomon(infra::ByteRange coeff); + + void Remainder(uint8_t* data, uint8_t length, uint8_t* result, uint8_t stride) const; + + private: + uint8_t Multiply(uint8_t x, uint8_t y) const; + + private: + infra::ByteRange coeff; + }; + + private: + uint8_t version; + QrCodeEcc ecc; + uint16_t moduleCount; + uint16_t bitOffset = 0; + infra::ByteRange data; + infra::ByteRange result; + ReedSolomon reedSolomon; + }; + + template + struct TextEncoder::ForVersionAndEcc + { + ForVersionAndEcc(); + + static constexpr uint16_t moduleCount = numRawDataModulesForVersion[Version]; + std::array data{}; + std::array result{}; + std::array coeff{}; + + TextEncoder buffer; + }; + + class QrCodeGenerator + { + public: + template + struct ForVersionAndEcc; + + QrCodeGenerator(infra::Bitmap& modules, infra::Bitmap& isFunction, TextEncoder& encoder, infra::ByteRange alignPosition, uint8_t version, QrCodeEcc ecc); + + void Generate(infra::BoundedConstString text); + + private: + uint8_t BestMask(); + + void ApplyMask(uint8_t mask); + void DrawFunctionPatterns(); + void DrawVersion(); + void DrawFinderPattern(infra::Point position); + void DrawAlignmentPattern(infra::Point position); + void DrawCodewords(); + void DrawFormatBits(uint8_t mask); + void SetFunctionModule(infra::Point position, bool on); + + private: + uint8_t version; + QrCodeEcc ecc; + + infra::Bitmap& modules; + infra::Bitmap& isFunction; + + TextEncoder& encoder; + + infra::ByteRange alignPosition; + }; + + template + struct QrCodeGenerator::ForVersionAndEcc + : QrCodeGenerator + { + ForVersionAndEcc(infra::Bitmap& modules); + + typename QrCode::BitmapType isFunction; + TextEncoder::ForVersionAndEcc encoder; + std::array alignPosition; + }; + } + + //// Implementation //// + + namespace detail + { + template + TextEncoder::ForVersionAndEcc::ForVersionAndEcc() + : buffer(data, result, coeff, Version, Ecc) + {} + + template + QrCodeGenerator::ForVersionAndEcc::ForVersionAndEcc(infra::Bitmap& modules) + : QrCodeGenerator(modules, isFunction, encoder.buffer, alignPosition, Version, Ecc) + { + isFunction.Clear(); + } + } + + template + QrCode::QrCode(infra::BoundedConstString text) + { + Update(text); + } + + template + void QrCode::Update(infra::BoundedConstString text) + { + this->Clear(); + detail::QrCodeGenerator::ForVersionAndEcc generator(*this); + generator.Generate(text); + } + + template + constexpr std::size_t QrCode::MaxSizeNumeric() + { + return detail::TextEncoder::MaxSizeNumeric(Version, Ecc); + } + + template + constexpr std::size_t QrCode::MaxSizeAlphanumeric() + { + return detail::TextEncoder::MaxSizeAlphanumeric(Version, Ecc); + } + + template + constexpr std::size_t QrCode::MaxSizeLatin1() + { + return detail::TextEncoder::MaxSizeLatin1(Version, Ecc); + } +} + +#endif diff --git a/preview/interfaces/test/TestGeometry.cpp b/preview/interfaces/test/TestGeometry.cpp index 3572e76..496d5d6 100644 --- a/preview/interfaces/test/TestGeometry.cpp +++ b/preview/interfaces/test/TestGeometry.cpp @@ -207,6 +207,16 @@ TEST(GeometryTest, RegionOffset) EXPECT_EQ(infra::Region(infra::Point(1, 2), infra::Vector(3, 4)), infra::Region(infra::Point(1, 2), infra::Vector(3, 4)) + infra::RegionOffset({ 1, 0 }, { 1, 50 }, { 1, 0 }, { 1, 50 }) - infra::RegionOffset({ 1, 0 }, { 1, 50 }, { 1, 0 }, { 1, 50 })); } +TEST(GeometryTest, RowFirstPoints) +{ + std::vector points; + + for (auto i : infra::RowFirstPoints(infra::Region(infra::Point(1, 2), infra::Point(3, 5)))) + points.push_back(i); + + EXPECT_EQ((std::vector{ { 1, 2 }, { 2, 2 }, { 1, 3 }, { 2, 3 }, { 1, 4 }, { 2, 4 } }), points); +} + TEST(GeometryTest, RestrictedSum) { EXPECT_EQ(9, infra::RestrictedInt16Sum(5, 4)); @@ -230,6 +240,16 @@ TEST(GeometryTest, ManhattanDistance) EXPECT_EQ(20, infra::ManhattanDistance(infra::Point(10, 10), infra::Point())); } +TEST(GeometryTest, ChebyshevDistance) +{ + EXPECT_EQ(0, infra::ChebyshevDistance(infra::Point(), infra::Point())); + EXPECT_EQ(10, infra::ChebyshevDistance(infra::Point(), infra::Point(10, 0))); + EXPECT_EQ(10, infra::ChebyshevDistance(infra::Point(), infra::Point(0, 10))); + EXPECT_EQ(10, infra::ChebyshevDistance(infra::Point(10, 0), infra::Point())); + EXPECT_EQ(10, infra::ChebyshevDistance(infra::Point(0, 10), infra::Point())); + EXPECT_EQ(10, infra::ChebyshevDistance(infra::Point(10, 10), infra::Point())); +} + TEST(GeometryTest, Aligned) { EXPECT_EQ(infra::Region(infra::Point(2, 3), infra::Vector(4, 6)), infra::AlignedRegion(infra::Region(infra::Point(2, 3), infra::Vector(4, 6)), 2, 3)); diff --git a/preview/sdl/DirectDisplaySdl.cpp b/preview/sdl/DirectDisplaySdl.cpp index bbcf075..164bc6a 100644 --- a/preview/sdl/DirectDisplaySdl.cpp +++ b/preview/sdl/DirectDisplaySdl.cpp @@ -343,7 +343,7 @@ namespace hal for (auto x = 0; x != sourceBitmap.size.deltaX; ++x) { auto i = y * sourceBitmap.size.deltaX + x; - auto colour = (sourceBitmap.buffer[i / 8] & (1 << (7 - i % 8))) == 0 ? infra::Colour::black : infra::Colour::white; + auto colour = (sourceBitmap.buffer[i / 8] & (1 << (i % 8))) == 0 ? infra::Colour::black : infra::Colour::white; DrawPixel(destination.TopLeft() + infra::Vector(x, y), colour, boundingBox); } } diff --git a/preview/views/ViewBitmap.cpp b/preview/views/ViewBitmap.cpp index bc3cba6..30f3db9 100644 --- a/preview/views/ViewBitmap.cpp +++ b/preview/views/ViewBitmap.cpp @@ -2,7 +2,7 @@ namespace services { - ViewBitmap::ViewBitmap(infra::Bitmap& bitmap) + ViewBitmap::ViewBitmap(const infra::Bitmap& bitmap) : source(bitmap) { Resize(bitmap.size); @@ -13,13 +13,13 @@ namespace services canvas.DrawBitmap(ViewRegion().TopLeft(), source, ViewRegion() & boundingRegion); } - void ViewBitmap::Source(infra::Bitmap& source) + void ViewBitmap::Source(const infra::Bitmap& source) { this->source = source; Dirty(ViewRegion()); } - infra::Bitmap& ViewBitmap::Source() const + const infra::Bitmap& ViewBitmap::Source() const { return source; } diff --git a/preview/views/ViewBitmap.hpp b/preview/views/ViewBitmap.hpp index 85fbc2e..9a6ba20 100644 --- a/preview/views/ViewBitmap.hpp +++ b/preview/views/ViewBitmap.hpp @@ -10,16 +10,16 @@ namespace services : public View { public: - explicit ViewBitmap(infra::Bitmap& bitmap); + explicit ViewBitmap(const infra::Bitmap& bitmap); // Implementation of View void Paint(hal::Canvas& canvas, infra::Region boundingRegion) override; - void Source(infra::Bitmap& source); - infra::Bitmap& Source() const; + void Source(const infra::Bitmap& source); + const infra::Bitmap& Source() const; private: - infra::Bitmap& source; + infra::Bitmap source; }; }