From fc1510bc75f54d8bc693a775f69bfac7db6d76d2 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Wed, 29 Jan 2025 15:08:06 +0100 Subject: [PATCH] Win32: Add WICBitmapForData A IWICBitmap implementation that wraps existing memory buffers --- src/meson.build | 1 + src/win32/wicbitmapfordata.cpp | 597 +++++++++++++++++++++++++++++++++ src/win32/wicbitmapfordata.hpp | 315 +++++++++++++++++ 3 files changed, 913 insertions(+) create mode 100644 src/win32/wicbitmapfordata.cpp create mode 100644 src/win32/wicbitmapfordata.hpp diff --git a/src/meson.build b/src/meson.build index ac06ac61a..2137c8cb1 100644 --- a/src/meson.build +++ b/src/meson.build @@ -181,6 +181,7 @@ cairo_feature_sources = { ], 'cairo-dwrite-font': [ 'win32/cairo-dwrite-font.cpp', + 'win32/wicbitmapfordata.cpp', ], 'cairo-script': [ 'cairo-script-surface.c', diff --git a/src/win32/wicbitmapfordata.cpp b/src/win32/wicbitmapfordata.cpp new file mode 100644 index 000000000..087a4cd12 --- /dev/null +++ b/src/win32/wicbitmapfordata.cpp @@ -0,0 +1,597 @@ +/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */ +/* Cairo - a vector graphics library with display and print output + * + * Copyright © 2025 Luca Bacci + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation < licensing@fsf.org >. You should have received a copy of + * the MPL along with this library in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * https://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + * The Original Code is the cairo graphics library. + * + * Contributor(s): + * Luca Bacci + */ + +#include "cairoint.h" + +#include "wicbitmapfordata.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if _WIN32_WINNT < _WIN32_WINNT_WIN8 +# define CHECK_FAST_FAIL IsProcessorFeaturePresent (PF_FASTFAIL_AVAILABLE) +#else +# define CHECK_FAST_FAIL 1 +#endif + +#ifndef FAST_FAIL_INVALID_REFERENCE_COUNT +#define FAST_FAIL_INVALID_REFERENCE_COUNT 14 +#endif + +constexpr double DEFAULT_DPI = 96.0; + +static void +print_unhandled_iid (const char *context, + REFIID riid); + +thread_local ULONG +WICBitmapForData::s_thread_instances_count; + +thread_local ULONG +WICBitmapLockForData::s_thread_instances_count; + +// WICBitmapForData + +WICBitmapForData::WICBitmapForData (unsigned char * const data, + UINT width, + UINT height, + UINT stride, + UINT pixel_size, + WICPixelFormatGUID wic_pixel_format, + deleter_t deleter) noexcept + : m_data (data) + , m_width (width) + , m_height (height) + , m_stride (stride) + , m_pixel_size (pixel_size) + , m_wic_pixel_format (wic_pixel_format) + , m_dpi_x (DEFAULT_DPI) + , m_dpi_y (DEFAULT_DPI) + , m_write_lock () + , m_read_locks_count (0) + , m_reference_count (deleter ? 1 : 0) + , m_deleter (deleter) + , m_thread_id (GetCurrentThreadId ()) +{ + assert (m_data != nullptr); + + assert (m_width != 0); + assert (m_height != 0); + assert (m_pixel_size != 0); + + assert (m_stride / m_pixel_size >= m_width); + assert (UINT_MAX / stride >= m_height); + + // Technically, should be m_stride * (m_height - 1) + m_row_size + assert (UINTPTR_MAX - (std::uintptr_t)m_data >= m_stride * m_height); + + s_thread_instances_count++; +} + +WICBitmapForData::~WICBitmapForData () noexcept +{ + method_check (); + + assert (!have_locks ()); + assert (m_reference_count == 0); + + s_thread_instances_count--; +} + +IFACEMETHODIMP +WICBitmapForData::QueryInterface (REFIID riid, + void **ppvObject) noexcept +{ + method_check (); + + if (riid == IID_IUnknown || + riid == IID_IWICBitmapSource || + riid == IID_IWICBitmap) + { + AddRef(); + *ppvObject = this; + return S_OK; + } + + print_unhandled_iid ("WICBitmapForData", riid); + *ppvObject = nullptr; + + return E_NOINTERFACE; +} + +IFACEMETHODIMP_(ULONG) +WICBitmapForData::AddRef () noexcept +{ + method_check (); + + if (m_reference_count >= s_reference_count_limit) { + if (CHECK_FAST_FAIL) { + __fastfail (FAST_FAIL_INVALID_REFERENCE_COUNT); + } + std::abort (); + } + + m_reference_count++; + + return m_reference_count; +} + +IFACEMETHODIMP_(ULONG) +WICBitmapForData::Release () noexcept +{ + method_check (); + + if (m_reference_count == 0 || m_reference_count > s_reference_count_limit) { + if (CHECK_FAST_FAIL) { + __fastfail (FAST_FAIL_INVALID_REFERENCE_COUNT); + } + std::abort (); + } + + m_reference_count--; + + assert (m_reference_count >= m_read_locks_count); + + if (m_reference_count == 0) { + if (m_deleter) { + m_deleter (this); + } + + // Note: the object has been destroyed, do not access any + // member method, call any member function, or use this. + return 0; + } + + return m_reference_count; +} + +// IWICBitmapSource +IFACEMETHODIMP +WICBitmapForData::CopyPalette (IWICPalette *pIPalette) noexcept +{ + method_check (); + + return WINCODEC_ERR_PALETTEUNAVAILABLE; +} + +IFACEMETHODIMP +WICBitmapForData::CopyPixels (const WICRect *prc, + UINT cbStride, + UINT cbBufferSize, + BYTE *pbBuffer) noexcept +{ + method_check (); + + if (have_write_locks ()) { + return WINCODEC_ERR_ALREADYLOCKED; + } + + UINT x = 0; + UINT y = 0; + UINT width = m_width; + UINT height = m_height; + + if (prc) { + if (!check_sub_rectangle (*prc)) + return E_INVALIDARG; + + x = static_cast (prc->X); + y = static_cast (prc->Y); + width = static_cast (prc->Width); + height = static_cast (prc->Height); + } + + const unsigned char *src_iter = get_address_for_point (x, y); + unsigned char *dst_iter = pbBuffer; + const UINT row_size = m_width * m_pixel_size; + + for (UINT i = 0; i < height; i++) { + std::memcpy (dst_iter, src_iter, row_size); + + src_iter += m_stride; + dst_iter += cbStride; + } + + return S_OK; +} + +IFACEMETHODIMP +WICBitmapForData::GetPixelFormat (WICPixelFormatGUID *pPixelFormat) noexcept +{ + method_check (); + + *pPixelFormat = m_wic_pixel_format; + + return S_OK; +} + +IFACEMETHODIMP +WICBitmapForData::GetResolution (double *pDpiX, + double *pDpiY) noexcept +{ + method_check (); + + *pDpiX = m_dpi_x; + *pDpiY = m_dpi_y; + + return S_OK; +} + +IFACEMETHODIMP +WICBitmapForData::GetSize (UINT *puiWidth, + UINT *puiHeight) noexcept +{ + method_check (); + + *puiWidth = m_width; + *puiHeight = m_height; + + return S_OK; +} + +IFACEMETHODIMP +WICBitmapForData::Lock (const WICRect *prcLock, + DWORD flags, + IWICBitmapLock **ppILock) noexcept +{ + method_check (); + + *ppILock = nullptr; + + UINT x = 0; + UINT y = 0; + UINT width = m_width; + UINT height = m_height; + + if (prcLock) { + if (!check_sub_rectangle (*prcLock)) + return E_INVALIDARG; + + x = static_cast (prcLock->X); + y = static_cast (prcLock->Y); + width = static_cast (prcLock->Width); + height = static_cast (prcLock->Height); + } + + const bool is_write_lock = (flags & WICBitmapLockWrite) != 0; + + if (is_write_lock) { + if (have_locks ()) { + return WINCODEC_ERR_ALREADYLOCKED; + } + + AddRef (); + + const auto& deleter = [](WICBitmapLockForData *lock) noexcept { + WICBitmapForData& bitmap = lock->m_parent_bitmap; + + assert (bitmap.m_write_lock.has_value ()); + bitmap.m_write_lock.reset (); + + bitmap.Release (); + }; + + // There can only be one write lock, and we made it a member of this class + m_write_lock.emplace(*this, x, y, width, height, deleter); + + *ppILock = &m_write_lock.value (); + } + else { + if (have_write_locks ()) { + return WINCODEC_ERR_ALREADYLOCKED; + } + + if (m_read_locks_count >= s_reference_count_limit - 1) { + if (CHECK_FAST_FAIL) { + __fastfail (FAST_FAIL_INVALID_REFERENCE_COUNT); + } + std::abort (); + } + + AddRef (); + m_read_locks_count++; + + const auto& deleter = [](WICBitmapLockForData *lock) noexcept { + WICBitmapForData& bitmap = lock->m_parent_bitmap; + + delete lock; + lock = nullptr; + + if (bitmap.m_read_locks_count == 0) { + if (CHECK_FAST_FAIL) { + __fastfail (FAST_FAIL_INVALID_REFERENCE_COUNT); + } + std::abort (); + } + + bitmap.m_read_locks_count--; + bitmap.Release (); + }; + + // Read locks are allocated on the heap + *ppILock = new (std::nothrow) WICBitmapLockForData (*this, x, y, width, height, deleter); + if (*ppILock == nullptr) { + Release (); + m_read_locks_count--; + + return E_OUTOFMEMORY; + } + } + + return S_OK; +} + +IFACEMETHODIMP +WICBitmapForData::SetPalette (IWICPalette *pIPalette) noexcept +{ + method_check (); + + return WINCODEC_ERR_PALETTEUNAVAILABLE; +} + +IFACEMETHODIMP +WICBitmapForData::SetResolution (double dpiX, + double dpiY) noexcept +{ + method_check (); + + m_dpi_x = dpiX; + m_dpi_y = dpiY; + + return S_OK; +} + +unsigned char * +WICBitmapForData::get_address_for_point (UINT x, UINT y) const noexcept +{ + static_assert (sizeof (*m_data) == 1, ""); + + return m_data + (y * m_stride) + (m_pixel_size * x); +} + +bool +WICBitmapForData::check_sub_rectangle (const WICRect& rectangle) const noexcept +{ + return rectangle.X >= 0 && + rectangle.Y >= 0 && + rectangle.Width > 0 && + rectangle.Height > 0 && + (UINT)rectangle.X < m_width && + (UINT)rectangle.Y < m_height && + m_width - (UINT)rectangle.X <= (UINT)rectangle.Width && + m_height - (UINT)rectangle.Y <= (UINT)rectangle.Height; +} + +bool +WICBitmapForData::have_write_locks () const noexcept +{ + return m_write_lock.has_value (); +} + +bool +WICBitmapForData::have_read_locks () const noexcept +{ + return m_read_locks_count != 0; +} + +bool +WICBitmapForData::have_locks () const noexcept +{ + return have_read_locks () || have_write_locks (); +} + +void +WICBitmapForData::method_check () const noexcept +{ + assert (m_thread_id == GetCurrentThreadId ()); +} + +// WICBitmapLockForData + +WICBitmapLockForData::WICBitmapLockForData (WICBitmapForData& parent_bitmap, + UINT x, + UINT y, + UINT width, + UINT height, + deleter_t deleter) noexcept + : m_parent_bitmap (parent_bitmap) + , m_x (x) + , m_y (y) + , m_width (width) + , m_height (height) + , m_reference_count (1) + , m_deleter (deleter) + , m_thread_id (GetCurrentThreadId ()) +{ + assert (m_x < m_parent_bitmap.m_width); + assert (m_y < m_parent_bitmap.m_height); + + assert (m_width <= m_parent_bitmap.m_width); + assert (m_height <= m_parent_bitmap.m_height); + + assert (m_parent_bitmap.m_width - m_width >= m_x); + assert (m_parent_bitmap.m_height - m_height >= m_y); + + s_thread_instances_count++; +} + +WICBitmapLockForData::~WICBitmapLockForData () noexcept +{ + method_check (); + + assert (m_reference_count == 0); + + s_thread_instances_count--; +} + +IFACEMETHODIMP +WICBitmapLockForData::QueryInterface (REFIID riid, + void **ppvObject) noexcept +{ + method_check (); + + if (riid == IID_IUnknown || + riid == IID_IWICBitmapLock) + { + AddRef(); + *ppvObject = this; + return S_OK; + } + + print_unhandled_iid ("WICBitmapLockForData", riid); + *ppvObject = nullptr; + + return E_NOINTERFACE; +} + +IFACEMETHODIMP_(ULONG) +WICBitmapLockForData::AddRef () noexcept +{ + method_check (); + + if (m_reference_count >= s_reference_count_limit) { + if (CHECK_FAST_FAIL) { + __fastfail (FAST_FAIL_INVALID_REFERENCE_COUNT); + } + std::abort (); + } + + m_reference_count++; + + return m_reference_count; +} + +IFACEMETHODIMP_(ULONG) +WICBitmapLockForData::Release () noexcept +{ + method_check (); + + if (m_reference_count == 0) { + if (CHECK_FAST_FAIL) { + __fastfail (FAST_FAIL_INVALID_REFERENCE_COUNT); + } + std::abort (); + } + + m_reference_count--; + + if (m_reference_count == 0) { + if (m_deleter) { + m_deleter (this); + } + + // Note: the object has been destroyed, do not access any + // member method, call any member function, or use this. + return 0; + } + + return m_reference_count; +} + +IFACEMETHODIMP +WICBitmapLockForData::GetDataPointer (UINT *pcbBufferSize, + WICInProcPointer *ppbData) noexcept +{ + method_check (); + + const UINT stride = m_parent_bitmap.m_stride; + const UINT pixel_size = m_parent_bitmap.m_pixel_size; + + *pcbBufferSize = stride * (m_height - 1) + (pixel_size * m_width); + *ppbData = m_parent_bitmap.get_address_for_point (m_x, m_y); + + return S_OK; +} + +IFACEMETHODIMP +WICBitmapLockForData::GetPixelFormat (WICPixelFormatGUID *pPixelFormat) noexcept +{ + method_check (); + + *pPixelFormat = m_parent_bitmap.m_wic_pixel_format; + + return S_OK; +} + +IFACEMETHODIMP +WICBitmapLockForData::GetSize (UINT *puiWidth, + UINT *puiHeight) noexcept +{ + method_check (); + + *puiWidth = m_width; + *puiHeight = m_height; + + return S_OK; +} + +IFACEMETHODIMP +WICBitmapLockForData::GetStride (UINT *pcbStride) noexcept +{ + method_check (); + + *pcbStride = m_parent_bitmap.m_stride; + + return S_OK; +} + +void +WICBitmapLockForData::method_check () const noexcept +{ + assert (m_thread_id == GetCurrentThreadId ()); +} + +// Utils + +static void +print_unhandled_iid (const char *context, + REFIID riid) +{ + static_assert (alignof (GUID) % sizeof (unsigned short) == 0, ""); + static_assert (offsetof (GUID, Data4) % sizeof (unsigned short) == 0, ""); + unsigned short *data4 = (unsigned short *)riid.Data4; + + std::fprintf (stderr, + "%s: %s {%08lX-%04hX-%04hX-%04hX-%04hX%04hX%04hX}\n", + context, "Could not handle QueryInterface for IID", + riid.Data1, riid.Data2, riid.Data3, + data4[0], data4[1], data4[2], data4[3]); +} diff --git a/src/win32/wicbitmapfordata.hpp b/src/win32/wicbitmapfordata.hpp new file mode 100644 index 000000000..1554191c8 --- /dev/null +++ b/src/win32/wicbitmapfordata.hpp @@ -0,0 +1,315 @@ +/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */ +/* Cairo - a vector graphics library with display and print output + * + * Copyright © 2025 Luca Bacci + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 31 Milk Street, # 960789 Boston, MA 02196 USA. + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * https://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + * The Original Code is the cairo graphics library. + * + * Contributor(s): + * Luca Bacci + */ + +#pragma once + +#include "cairoint.h" + +#include +#include +#include + +#include "optional.hpp" + +/* WICBitmapForData (bitmap), WICBitmapLockForData (lock). + * + * WICBitmapForData is an implementation of IWICBitmap that wraps an + * existing memory buffer. This is needed to make Direct2D render + * directly on in-memory graphics data that is created externally, + * provided that pixel formats match. Note that WIC supports many + * pixel formats natively [1], but Direct2D WIC render targets support + * only a few selected formats [2]. + * + * Constructor: + * + * WICBitmapForData (unsigned char * const data, + * UINT width, + * UINT height, + * UINT stride, + * UINT pixel_size, + * WICPixelFormatGUID wic_pixel_format, + * deleter_t deleter); + * + * @data: pointer to the memory buffer containing graphics data + * + * @width: the image width (cannot be zero) + * + * @height: the image height (cannot be zero) + * + * @stride: the size (in bytes) to move from one row to the next + * + * @pixel_size: pizel size (in bytes). As this argument is a byte size, + * WICBitmapForData cannot support packed pixel formats + * (e.g A1). Supporting such formats would complicate the + * implementation, and is not needed for Direct2D anyway. + * + * @wic_pixel_format: the WICPixelFormatGUID of the image data + * + * @deleter: a deleter callback with signature: + * + * void (*)(WICBitmapForData*) noexcept + * + * It will be called once the reference count of the object + * drops to zero. If lifetime should be managed externally, + * pass nullptr. + * + * Notes: + * + * The pixel size could be derived automatically from the pixel format + * GUID, however that's a quite elaborated process, which involve + * potential runtime activation of COM objects due to the extensible + * nature of WIC. That can be slow to do repeatedly, and renders the + * constructor failable, adding complexity. + * + * WICBitmapForData can be allocated by whatever mean, thanks to the + * deleter construct-time argument. This was added because most COM + * objects trigger their destruction internally from the Release() + * method, however, the object doesn't know how the user allocated it. + * The user passes a deleter callback which is responsible for deleting + * the instance. + * + * In some cases, lifetime (deletion) of the object is outside the control + * of the object user; for example, when the object is allocated on the + * stack or within another structure that it's not owned. In such cases, + * a nullptr deleter can ber specified. Internal reference counting is + * kept for debugging purposes, but won't trigger destruction. The user + * need not drop its own initial reference count. + * + * WICBitmapForData only supports pixel formats where pixels are byte-aligned. + * That is, bits-per-pixel must be a multiple of eight. See the @pixel_size + * argument description. + * + * WICBitmapForData is meant for use from a single thread only. + * + * References: + * + * [1] - https://learn.microsoft.com/en-us/windows/win32/wic/-wic-codec-native-pixel-formats + * [2] - https://learn.microsoft.com/en-us/windows/win32/direct2d/supported-pixel-formats-and-alpha-modes#supported-formats-for-wic-bitmap-render-target + */ + +class WICBitmapForData; +class WICBitmapLockForData; + +#ifndef _MSC_VER +// mingw-w64 headers don't have WICInProcPointer +typedef BYTE* WICInProcPointer; +#endif + +// We define the lock class first, as we want to make the +// lock class (write lock) a member of the bitmap class. + +// https://stackoverflow.com/questions/27489558/how-do-i-create-an-alias-for-a-noexcept-function-pointer +void lock_deleter_prototype (WICBitmapLockForData*) noexcept; + +class WICBitmapLockForData final + : public IWICBitmapLock +{ +public: + // Cannot use noexcept in using declarations, so we use + // a function declaration and decltype as a workaround. + using deleter_t = decltype (&lock_deleter_prototype); + // TODO: Use a functor / callable object + + // Constructor + explicit WICBitmapLockForData (WICBitmapForData& parent_bitmap, + UINT x, + UINT y, + UINT width, + UINT height, + deleter_t deleter) noexcept; + +public: + // Disable copy + WICBitmapLockForData (const WICBitmapLockForData&) = delete; + WICBitmapLockForData& operator= (const WICBitmapLockForData&) = delete; + + // Destructor + ~WICBitmapLockForData () noexcept; + + // IUnknown + IFACEMETHOD (QueryInterface) (REFIID riid, + void **ppvObject) noexcept override; + + IFACEMETHOD_(ULONG, AddRef) () noexcept override; + + IFACEMETHOD_(ULONG, Release) () noexcept override; + + // IWICBitmapLock + IFACEMETHOD (GetDataPointer) (UINT *pcbBufferSize, + WICInProcPointer *ppbData) noexcept override; + + IFACEMETHOD (GetPixelFormat) (WICPixelFormatGUID *pPixelFormat) noexcept override; + + IFACEMETHOD (GetSize) (UINT *puiWidth, + UINT *puiHeight) noexcept override; + + IFACEMETHOD (GetStride) (UINT *pcbStride) noexcept override; + +private: + void method_check () const noexcept; + +private: + WICBitmapForData& m_parent_bitmap; + + const UINT m_x; + const UINT m_y; + const UINT m_width; + const UINT m_height; + + ULONG m_reference_count; + + const deleter_t m_deleter; + + const DWORD m_thread_id; + + friend class WICBitmapForData; + +private: + static constexpr ULONG s_reference_count_limit { + std::numeric_limits::max() + }; + + static thread_local ULONG s_thread_instances_count; +}; + +void bitmap_deleter_prototype (WICBitmapForData*) noexcept; + +class WICBitmapForData final + : public IWICBitmap +{ +public: + // Disable copy + WICBitmapForData (const WICBitmapForData&) = delete; + WICBitmapForData& operator= (const WICBitmapForData&) = delete; + + // Cannot use noexcept in using declarations, so we use + // a function declaration and decltype as a workaround. + using deleter_t = decltype (&bitmap_deleter_prototype); + // TODO: Use a functor / callable object + + // Constructor + explicit WICBitmapForData (unsigned char * const data, + UINT width, + UINT height, + UINT stride, + UINT pixel_size, + WICPixelFormatGUID wic_pixel_format, + deleter_t deleter) noexcept; + + // Destructor + ~WICBitmapForData () noexcept; + + // IUnknown + IFACEMETHOD (QueryInterface) (REFIID riid, + void **ppvObject) noexcept override; + + IFACEMETHOD_(ULONG, AddRef) () noexcept override; + + IFACEMETHOD_(ULONG, Release) () noexcept override; + + // IWICBitmapSource + IFACEMETHOD (CopyPalette) (IWICPalette *pIPalette) noexcept override; + + IFACEMETHOD (CopyPixels) (const WICRect *prc, + UINT cbStride, + UINT cbBufferSize, + BYTE *pbBuffer) noexcept override; + + IFACEMETHOD (GetPixelFormat) (WICPixelFormatGUID *pPixelFormat) noexcept override; + + IFACEMETHOD (GetResolution) (double *pDpiX, + double *pDpiY) noexcept override; + + IFACEMETHOD (GetSize) (UINT *puiWidth, + UINT *puiHeight) noexcept override; + + // IWICBitmap + IFACEMETHOD (Lock) (const WICRect *prcLock, + DWORD flags, + IWICBitmapLock **ppILock) noexcept override; + + IFACEMETHOD (SetPalette) (IWICPalette *pIPalette) noexcept override; + + IFACEMETHOD (SetResolution) (double dpiX, + double dpiY) noexcept override; + +private: + unsigned char * get_address_for_point (UINT x, + UINT y) const noexcept; + + bool check_sub_rectangle (const WICRect& rectangle) const noexcept; + + bool have_write_locks () const noexcept; + bool have_read_locks () const noexcept; + bool have_locks () const noexcept; + + // Check for public methods + void method_check () const noexcept; + +private: + unsigned char * const m_data; + const UINT m_width; + const UINT m_height; + const UINT m_stride; + const UINT m_pixel_size; + const WICPixelFormatGUID m_wic_pixel_format; + + double m_dpi_x; + double m_dpi_y; + + // There can only be one write lock at a time. Make it + // a member so that we avoid an allocation on the heap. + tl::optional m_write_lock; + + // Keeps track of how many read locks are outstanding. + ULONG m_read_locks_count; + + ULONG m_reference_count; + + const deleter_t m_deleter; + + // The thread ID at construction time. This class is + // meant to be used by one thread only, so we check + // the thread ID in all public methods. + const DWORD m_thread_id; + + friend class WICBitmapLockForData; + +private: + static constexpr ULONG s_reference_count_limit { + std::numeric_limits::max() + }; + + static thread_local ULONG s_thread_instances_count; +};