CPGFImage(3)
๐ธ CPGFImage โ the main character of libpgf, section 3 ๐ธ
okay so real talk: this is the page for CPGFImage(3) from libpgf, and she is the it-girl class of the whole library. No cap.
โจ NAME โจ
CPGFImage โ PGF main class. literally the whole vibe, she runs the show ๐
๐ซถ SYNOPSIS ๐ซถ
#include <PGFimage.h>
๐ Public Member Functions
CPGFImage ()โ Standard constructor.virtual ~CPGFImage ()โ Destructor.void Destroy ()void Open (CPGFStream *stream)bool IsOpen () constโ Returnstrueif the PGF has been opened for reading.void Read (int level=0, CallbackPtr cb=nullptr, void *data=nullptr)void Read (PGFRect &rect, int level=0, CallbackPtr cb=nullptr, void *data=nullptr)void ReadPreview ()void Reconstruct (int level=0)void GetBitmap (int pitch, UINT8 *buff, BYTE bpp, int channelMap[]=nullptr, CallbackPtr cb=nullptr, void *data=nullptr) constvoid GetYUV (int pitch, DataT *buff, BYTE bpp, int channelMap[]=nullptr, CallbackPtr cb=nullptr, void *data=nullptr) constvoid ImportBitmap (int pitch, UINT8 *buff, BYTE bpp, int channelMap[]=nullptr, CallbackPtr cb=nullptr, void *data=nullptr)void ImportYUV (int pitch, DataT *buff, BYTE bpp, int channelMap[]=nullptr, CallbackPtr cb=nullptr, void *data=nullptr)void Write (CPGFStream *stream, UINT32 *nWrittenBytes=nullptr, CallbackPtr cb=nullptr, void *data=nullptr)UINT32 WriteHeader (CPGFStream *stream)UINT32 WriteImage (CPGFStream *stream, CallbackPtr cb=nullptr, void *data=nullptr)UINT32 Write (int level, CallbackPtr cb=nullptr, void *data=nullptr)void ConfigureEncoder (bool useOMP=true, bool favorSpeedOverSize=false)void ConfigureDecoder (bool useOMP=true, UserdataPolicy policy=UP_CacheAll, UINT32 prefixSize=0)void ResetStreamPos (bool startOfData)void SetChannel (DataT *channel, int c=0)void SetHeader (const PGFHeader &header, BYTE flags=0, const UINT8 *userData=0, UINT32 userDataLength=0)void SetMaxValue (UINT32 maxValue)void SetProgressMode (ProgressMode pm)void SetRefreshCallback (RefreshCB callback, void *arg)void SetColorTable (UINT32 iFirstColor, UINT32 nColors, const RGBQUAD *prgbColors)DataT * GetChannel (int c=0)void GetColorTable (UINT32 iFirstColor, UINT32 nColors, RGBQUAD *prgbColors) constconst RGBQUAD * GetColorTable () constconst PGFHeader * GetHeader () constUINT32 GetMaxValue () constUINT64 GetUserDataPos () constconst UINT8 * GetUserData (UINT32 &cachedSize, UINT32 *pTotalSize=nullptr) constUINT32 GetEncodedHeaderLength () constUINT32 GetEncodedLevelLength (int level) constUINT32 ReadEncodedHeader (UINT8 *target, UINT32 targetLen) constUINT32 ReadEncodedData (int level, UINT8 *target, UINT32 targetLen) constUINT32 ChannelWidth (int c=0) constUINT32 ChannelHeight (int c=0) constBYTE ChannelDepth () constUINT32 Width (int level=0) constUINT32 Height (int level=0) constBYTE Level () constBYTE Levels () constbool IsFullyRead () constโ Returntrueif all levels have been read.BYTE Quality () constBYTE Channels () constBYTE Mode () constBYTE BPP () constbool ROIisSupported () constPGFRect ComputeLevelROI () constBYTE UsedBitsPerChannel () constBYTE Version () const
๐ Static Public Member Functions
static bool ImportIsSupported (BYTE mode)static UINT32 LevelSizeL (UINT32 size, int level)static UINT32 LevelSizeH (UINT32 size, int level)static BYTE CodecMajorVersion (BYTE version=PGFVersion)โ Return major version.static BYTE MaxChannelDepth (BYTE version=PGFVersion)
๐ง Protected Attributes
CWaveletTransform * m_wtChannel [MaxChannels]โ wavelet transformed color channelsDataT * m_channel [MaxChannels]โ untransformed channels in YUV formatCDecoder * m_decoderโ PGF decoder.CEncoder * m_encoderโ PGF encoder.UINT32 * m_levelLengthโ length of each level in bytes; first level starts immediately after this arrayUINT32 m_width [MaxChannels]โ width of each channel at current levelUINT32 m_height [MaxChannels]โ height of each channel at current levelPGFPreHeader m_preHeaderโ PGF pre-header.PGFHeader m_headerโ PGF file header.PGFPostHeader m_postHeaderโ PGF post-header.UINT64 m_userDataPosโ stream position of user dataint m_currentLevelโ transform level of current imageUINT32 m_userDataPolicyโ user data (metadata) policy during openBYTE m_quantโ quantization parameterbool m_downsampleโ chrominance channels are downsampledbool m_favorSpeedOverSizeโ favor encoding speed over compression ratiobool m_useOMPinEncoderโ use Open MP in encoderbool m_useOMPinDecoderโ use Open MP in decoderbool m_streamReinitializedโ stream has been reinitializedPGFRect m_roiโ region of interest
๐ Private Member Functions
void Init ()void ComputeLevels ()bool CompleteHeader ()void RgbToYuv (int pitch, UINT8 *rgbBuff, BYTE bpp, int channelMap[], CallbackPtr cb, void *data)void Downsample (int nChannel)UINT32 UpdatePostHeaderSize ()void WriteLevel ()PGFRect GetAlignedROI (int c=0) constvoid SetROI (PGFRect rect)UINT8 Clamp4 (DataT v) constUINT16 Clamp6 (DataT v) constUINT8 Clamp8 (DataT v) constUINT16 Clamp16 (DataT v) constUINT32 Clamp31 (DataT v) const
๐ Private Attributes
RefreshCB m_cbโ pointer to refresh callback procedurevoid * m_cbArgโ refresh callback argumentdouble m_percentโ progress[0..1]ProgressMode m_progressModeโ progress mode used inReadandWrite;PM_Relativeis default mode
๐ฆ Detailed Description ๐ฆ
PGF main class.
PGF image class is the main class. You always need a PGF object for encoding or decoding image data.
- Decoding:
Open()Read()GetBitmap() - Encoding:
SetHeader()ImportBitmap()Write()
๐ Author ๐
C. Stamm, R. Spuler
Definition at line 53 of file PGFimage.h.
๐ Constructor & Destructor Documentation ๐
CPGFImage::CPGFImage ()
Standard constructor.
Definition at line 64 of file PGFimage.cpp.
64 {
65 Init();
66 }
CPGFImage::~CPGFImage () [virtual]
Destructor.
Definition at line 117 of file PGFimage.cpp.
117 {
118 m_currentLevel = -100; // unusual value used as marker in Destroy()
119 Destroy();
120 }
๐ฅ Member Function Documentation ๐ฅ
BYTE CPGFImage::BPP () const [inline]
Return the number of bits per pixel. Valid values can be 1, 8, 12, 16, 24, 32, 48, 64.
Returns
Number of bits per pixel.
Definition at line 461 of file PGFimage.h.
461 { return m_header.bpp; }
BYTE CPGFImage::ChannelDepth () const [inline]
Return bits per channel of the image's encoder.
Returns
Bits per channel
Definition at line 406 of file PGFimage.h.
406 { return MaxChannelDepth(m_preHeader.version); }
UINT32 CPGFImage::ChannelHeight (int c = 0) const [inline]
Return current image height of given channel in pixels. The returned height depends on the levels read so far and on ROI. โจ (so yes, she's dynamic, she changes as you read more โ very main character of her)
Parameters
cโ A channel index
Returns
Channel height in pixels
Definition at line 401 of file PGFimage.h.
401 { ASSERT(c >= 0 && c < MaxChannels); return m_height[c]; }
BYTE CPGFImage::Channels () const [inline]
Return the number of image channels. An image of type RGB contains 3 image channels (B, G, R).
Returns
Number of image channels
Definition at line 448 of file PGFimage.h.
448 { return m_header.channels; }
UINT32 CPGFImage::ChannelWidth (int c = 0) const [inline]
Return current image width of given channel in pixels. The returned width depends on the levels read so far and on ROI.
Parameters
cโ A channel index
Returns
Channel width in pixels
Definition at line 394 of file PGFimage.h.
394 { ASSERT(c >= 0 && c < MaxChannels); return m_width[c]; }
UINT16 CPGFImage::Clamp16 (DataT v) const [inline], [private]
Definition at line 573 of file PGFimage.h.
573 {
574 if (v & 0xFFFF0000) return (v < 0) ? (UINT16)0: (UINT16)65535; else return (UINT16)v;
575 }
UINT32 CPGFImage::Clamp31 (DataT v) const [inline], [private]
Definition at line 576 of file PGFimage.h.
576 {
577 return (v < 0) ? 0 : (UINT32)v;
578 }
UINT8 CPGFImage::Clamp4 (DataT v) const [inline], [private]
Definition at line 563 of file PGFimage.h.
563 {
564 if (v & 0xFFFFFFF0) return (v < 0) ? (UINT8)0: (UINT8)15; else return (UINT8)v;
565 }
UINT16 CPGFImage::Clamp6 (DataT v) const [inline], [private]
Definition at line 566 of file PGFimage.h.
566 {
567 if (v & 0xFFFFFFC0) return (v < 0) ? (UINT16)0: (UINT16)63; else return (UINT16)v;
568 }
UINT8 CPGFImage::Clamp8 (DataT v) const [inline], [private]
Definition at line 569 of file PGFimage.h.
569 {
570 // needs only one test in the normal case
571 if (v & 0xFFFFFF00) return (v < 0) ? (UINT8)0 : (UINT8)255; else return (UINT8)v;
572 }
BYTE CPGFImage::CodecMajorVersion (BYTE version = PGFVersion) [static]
Return major version. Return codec major version.
Parameters
versionโ pgf pre-header version number
Returns
PGF major of given version
Definition at line 768 of file PGFimage.cpp.
768 {
769 if (version & Version7) return 7;
770 if (version & Version6) return 6;
771 if (version & Version5) return 5;
772 if (version & Version2) return 2;
773 return 1;
774 }
bestie this is only part 1 of 18 and we have SO much more class to get through โ keep scrolling, we're not even warmed up ๐
โจ bool CPGFImage::CompleteHeader () [private]
Definition at line 218 of file PGFimage.cpp.
218 {
219 // set current codec version
220 m_header.version = PGFVersionNumber(PGFMajorNumber, PGFYear, PGFWeek);
221
222 if (m_header.mode == ImageModeUnknown) {
223 // undefined mode
224 switch(m_header.bpp) {
225 case 1: m_header.mode = ImageModeBitmap; break;
226 case 8: m_header.mode = ImageModeGrayScale; break;
227 case 12: m_header.mode = ImageModeRGB12; break;
228 case 16: m_header.mode = ImageModeRGB16; break;
229 case 24: m_header.mode = ImageModeRGBColor; break;
230 case 32: m_header.mode = ImageModeRGBA; break;
231 case 48: m_header.mode = ImageModeRGB48; break;
232 default: m_header.mode = ImageModeRGBColor; break;
233 }
234 }
235 if (!m_header.bpp) {
236 // undefined bpp
237 switch(m_header.mode) {
238 case ImageModeBitmap:
239 m_header.bpp = 1;
240 break;
241 case ImageModeIndexedColor:
242 case ImageModeGrayScale:
243 m_header.bpp = 8;
244 break;
245 case ImageModeRGB12:
246 m_header.bpp = 12;
247 break;
248 case ImageModeRGB16:
249 case ImageModeGray16:
250 m_header.bpp = 16;
251 break;
252 case ImageModeRGBColor:
253 case ImageModeLabColor:
254 m_header.bpp = 24;
255 break;
256 case ImageModeRGBA:
257 case ImageModeCMYKColor:
258 case ImageModeGray32:
259 m_header.bpp = 32;
260 break;
261 case ImageModeRGB48:
262 case ImageModeLab48:
263 m_header.bpp = 48;
264 break;
265 case ImageModeCMYK64:
266 m_header.bpp = 64;
267 break;
268 default:
269 ASSERT(false);
270 m_header.bpp = 24;
271 }
272 }
273 if (m_header.mode == ImageModeRGBColor && m_header.bpp == 32) {
274 // change mode
275 m_header.mode = ImageModeRGBA;
276 }
277 if (m_header.mode == ImageModeBitmap && m_header.bpp != 1) return false;
278 if (m_header.mode == ImageModeIndexedColor && m_header.bpp != 8) return false;
279 if (m_header.mode == ImageModeGrayScale && m_header.bpp != 8) return false;
280 if (m_header.mode == ImageModeGray16 && m_header.bpp != 16) return false;
281 if (m_header.mode == ImageModeGray32 && m_header.bpp != 32) return false;
282 if (m_header.mode == ImageModeRGBColor && m_header.bpp != 24) return false;
283 if (m_header.mode == ImageModeRGBA && m_header.bpp != 32) return false;
284 if (m_header.mode == ImageModeRGB12 && m_header.bpp != 12) return false;
285 if (m_header.mode == ImageModeRGB16 && m_header.bpp != 16) return false;
286 if (m_header.mode == ImageModeRGB48 && m_header.bpp != 48) return false;
287 if (m_header.mode == ImageModeLabColor && m_header.bpp != 24) return false;
288 if (m_header.mode == ImageModeLab48 && m_header.bpp != 48) return false;
289 if (m_header.mode == ImageModeCMYKColor && m_header.bpp != 32) return false;
290 if (m_header.mode == ImageModeCMYK64 && m_header.bpp != 64) return false;
291
292 // set number of channels
293 if (!m_header.channels) {
294 switch(m_header.mode) {
295 case ImageModeBitmap:
296 case ImageModeIndexedColor:
297 case ImageModeGrayScale:
298 case ImageModeGray16:
299 case ImageModeGray32:
300 m_header.channels = 1;
301 break;
302 case ImageModeRGBColor:
303 case ImageModeRGB12:
304 case ImageModeRGB16:
305 case ImageModeRGB48:
306 case ImageModeLabColor:
307 case ImageModeLab48:
308 m_header.channels = 3;
309 break;
310 case ImageModeRGBA:
311 case ImageModeCMYKColor:
312 case ImageModeCMYK64:
313 m_header.channels = 4;
314 break;
315 default:
316 return false;
317 }
318 }
319
320 // store used bits per channel
321 UINT8 bpc = m_header.bpp/m_header.channels;
322 if (bpc > 31) bpc = 31;
323 if (!m_header.usedBitsPerChannel || m_header.usedBitsPerChannel > bpc) {
324 m_header.usedBitsPerChannel = bpc;
325 }
326
327 return true;
328 }
๐ธ PGFRect CPGFImage::ComputeLevelROI () const
Returns the ROI of channel 0 at the current level, in pixels. The returned rect is only valid after you've read a ROI, bestie. Don't come for me if you use it before that.
Returns: ROI in pixels
Definition at line 583 of file PGFimage.cpp.
583 {
584 if (m_currentLevel == 0) {
585 return m_roi;
586 } else {
587 const UINT32 rLeft = LevelSizeL(m_roi.left, m_currentLevel);
588 const UINT32 rRight = LevelSizeL(m_roi.right, m_currentLevel);
589 const UINT32 rTop = LevelSizeL(m_roi.top, m_currentLevel);
590 const UINT32 rBottom = LevelSizeL(m_roi.bottom, m_currentLevel);
591 return PGFRect(rLeft, rTop, rRight - rLeft, rBottom - rTop);
592 }
593 }
๐
void CPGFImage::ComputeLevels () [private]
Definition at line 854 of file PGFimage.cpp.
854 {
855 const int maxThumbnailWidth = 20*FilterSize;
856 const int m = __min(m_header.width, m_header.height);
857 int s = m;
858
859 if (m_header.nLevels < 1 || m_header.nLevels > MaxLevel) {
860 m_header.nLevels = 1;
861 // compute a good value depending on the size of the image
862 while (s > maxThumbnailWidth) {
863 m_header.nLevels++;
864 s >>= 1;
865 }
866 }
867
868 int levels = m_header.nLevels; // we need a signed value during level reduction
869
870 // reduce number of levels if the image size is smaller than FilterSize*(2^levels)
871 s = FilterSize*(1 << levels); // must be at least the double filter size because of subsampling
872 while (m < s) {
873 levels--;
874 s >>= 1;
875 }
876 if (levels > MaxLevel) m_header.nLevels = MaxLevel;
877 else if (levels < 0) m_header.nLevels = 0;
878 else m_header.nLevels = (UINT8)levels;
879
880 // used in Write when PM_Absolute
881 m_percent = pow(0.25, m_header.nLevels);
882
883 ASSERT(0 <= m_header.nLevels && m_header.nLevels <= MaxLevel);
884 }
๐ฆ void CPGFImage::ConfigureDecoder (bool useOMP = true, UserdataPolicy policy = UP_CacheAll, UINT32 prefixSize = 0) [inline]
Configures the decoder. Let's get into the parameters, because they matter.
Parameters:
useOMPโ use parallel threading with Open MP during decoding. Default value:true. It only influences the decoding if the codec has been compiled with OpenMP support. No OpenMP, no extra sparkle.policyโ the file might contain user data (e.g. metadata). This policy defines the behaviour duringOpen().UP_CacheAll: user data is read and stored completely in a new allocated memory block. It can be accessed byGetUserData().UP_CachePrefix: onlyprefixSizebytes at the beginning of the user data are stored in a new allocated memory block. It can be accessed byGetUserData().UP_Skip: user data is skipped and nothing is cached, periodt.prefixSizeโ is only used in combination withUP_CachePrefix. It defines the number of bytes cached.
Definition at line 260 of file PGFimage.h.
260 { ASSERT(prefixSize <= MaxUserDataSize); m_useOMPinDecoder = useOMP; m_userDataPolicy = (UP_CachePrefix) ? prefixSize : 0xFFFFFFFF - policy; }
๐ void CPGFImage::ConfigureEncoder (bool useOMP = true, bool favorSpeedOverSize = false) [inline]
Configures the encoder. Two little switches, that's it.
Parameters:
useOMPโ use parallel threading with Open MP during encoding. Default value:true. Influences the encoding only if the codec has been compiled with OpenMP support.favorSpeedOverSizeโ favors encoding speed over compression ratio. Default value:false.
Definition at line 250 of file PGFimage.h.
250 { m_useOMPinEncoder = useOMP; m_favorSpeedOverSize = favorSpeedOverSize; }
๐ void CPGFImage::Destroy ()
Definition at line 124 of file PGFimage.cpp.
124 {
125 for (int i = 0; i < m_header.channels; i++) {
126 delete m_wtChannel[i]; // also deletes m_channel
127 }
128 delete[] m_postHeader.userData;
129 delete[] m_levelLength;
130 delete m_decoder;
131 delete m_encoder;
132
133 if (m_currentLevel != -100) Init();
134 }
void CPGFImage::Downsample (int nChannel) [private] ๐
okay so this is the lil glow-down moment โ it literally shrinks the channel down to half size by averaging pixel blocks together. it's private, so bestie, you're not calling this yourself unless you're inside the class. Definition at line 810 of file PGFimage.cpp.
810 {
811 ASSERT(ch > 0);
812
813 const int w = m_width[0];
814 const int w2 = w/2;
815 const int h2 = m_height[0]/2;
816 const int oddW = w%2; // don't use bool -> problems with MaxSpeed optimization
817 const int oddH = m_height[0]%2; // "
818 int loPos = 0;
819 int hiPos = w;
820 int sampledPos = 0;
821 DataT* buff = m_channel[ch]; ASSERT(buff);
822
823 for (int i=0; i < h2; i++) {
824 for (int j=0; j < w2; j++) {
825 // compute average of pixel block
826 buff[sampledPos] = (buff[loPos] + buff[loPos + 1] + buff[hiPos] + buff[hiPos + 1]) >> 2;
827 loPos += 2; hiPos += 2;
828 sampledPos++;
829 }
830 if (oddW) {
831 buff[sampledPos] = (buff[loPos] + buff[hiPos]) >> 1;
832 loPos++; hiPos++;
833 sampledPos++;
834 }
835 loPos += w; hiPos += w;
836 }
837 if (oddH) {
838 for (int j=0; j < w2; j++) {
839 buff[sampledPos] = (buff[loPos] + buff[loPos+1]) >> 1;
840 loPos += 2; hiPos += 2;
841 sampledPos++;
842 }
843 if (oddW) {
844 buff[sampledPos] = buff[loPos];
845 }
846 }
847
848 // downsampled image has half width and half height
849 m_width[ch] = (m_width[ch] + 1)/2;
850 m_height[ch] = (m_height[ch] + 1)/2;
851 }
a few little lore drops from this one, because it's giving attention to detail:
ASSERT(ch > 0);โ channel index has to be positive, no zero-index shenanigans here.oddWandoddHareint, notboolโ the comment says it's because of "problems with MaxSpeed optimization". the devs were like "don't use bool, i beg" and honestly iconic of them. ๐- the average is a straight-up
>> 2on four pixels (that's divide-by-4, bestie, shifts stay winning). m_width[ch]andm_height[ch]get halved with(x + 1)/2at the end, so odd dimensions round up. and yes, the comment confirms: "downsampled image has half width and half height".
PGFRect CPGFImage::GetAlignedROI (int c = 0) const [private] ๐
"Returns aligned ROI in pixels of current level of channel c" โ so it hands you back the region-of-interest rectangle, all nicely aligned, for whatever channel you ask about. ๐ฅ
Parameters
cโ A channel index
Definition at line 598 of file PGFimage.cpp.
598 {
599 PGFRect roi(0, 0, m_width[c], m_height[c]);
600
601 if (ROIisSupported()) {
602 ASSERT(m_wtChannel[c]);
603
604 roi = m_wtChannel[c]->GetAlignedROI(m_currentLevel);
605 }
606 ASSERT(roi.Width() == m_width[c]);
607 ASSERT(roi.Height() == m_height[c]);
608 return roi;
609 }
so it starts with a whole-image rect PGFRect roi(0, 0, m_width[c], m_height[c]) and then, if ROIisSupported(), it grabs the aligned ROI from the wavelet channel at m_currentLevel instead. either way it asserts that the width and height line up with m_width[c] and m_height[c] before returning. trust issues, but make it โจvalidationโจ.
void CPGFImage::GetBitmap (int pitch, UINT8 * buff, BYTE bpp, int channelMap[] = nullptr, CallbackPtr cb = nullptr, void * data = nullptr) const ๐
this is the big one, bestie. Get image data in interleaved format: (ordering of RGB data is BGR[A]). ๐
Upsampling, YUV to RGB transform and interleaving are done here to reduce the number of passes over the data.
okay now the pitch situation, listen up:
- The absolute value of
pitchis the number of bytes of an image row of the given image buffer. - If
pitchis negative, then the image buffer must point to the last row of a bottom-up image (first byte on last row). - if
pitchis positive, then the image buffer must point to the first row of a top-down image (first byte).
The sequence of output channels in the output image buffer does not need to be the same as provided by PGF. In case of different sequences you have to provide a channelMap of size of expected channels (depending on image mode).
For example, PGF provides a channel sequence BGR in RGB color mode. If your provided image buffer expects a channel sequence ARGB, then the channelMap looks like { 3, 2, 1, 0 }.
โ ๏ธ It might throw an IOException. so like, wrap it, bestie, don't be out here raw-dogging exceptions. ๐ญ
Parameters
pitchโ The number of bytes of a row of the image buffer.buffโ An image buffer.bppโ The number of bits per pixel used in image buffer.channelMapโ A integer array containing the mapping of PGF channel ordering to expected channel ordering.cbโ A pointer to a callback procedure. The procedure is called after each copied buffer row. Ifcbreturnstrue, then it stops proceeding.dataโ Data Pointer to C++ class container to host callback procedure.
okay bestie we are DEEP in the function body now and it's giving "the author left ALL the receipts" ๐ buckle up, because this part is a whole tour through the pixel-pushing machinery. Nothing is being skipped. Not one line. Not one assert.
Definition at line 1789 of file PGFimage.cpp.
1789 {
1790 ASSERT(buff);
1791 UINT32 w = m_width[0]; // width of decoded image
1792 UINT32 h = m_height[0]; // height of decoded image
1793 UINT32 yw = w; // y-channel width
1794 UINT32 uw = m_width[1]; // u-channel width
1795 UINT32 roiOffsetX = 0;
1796 UINT32 roiOffsetY = 0;
1797 UINT32 yOffset = 0;
1798 UINT32 uOffset = 0;
so first thing, we assert buff โ like, obviously, we need somewhere to put the pixels ๐ซถ. Then the local crew rolls in: w and h get pulled straight from m_width[0] and m_height[0] (that's the width and height of the decoded image, per the comments, bestie โ the comments are doing the work here), yw starts life as w and is labelled the y-channel width, and uw comes from m_width[1] โ the u-channel width. Then four offsets โ roiOffsetX, roiOffsetY, yOffset, uOffset โ all start at 0. Clean slate energy โจ.
1800 #ifdef __PGFROISUPPORT__
1801 const PGFRect& roi = GetAlignedROI(); // in pixels, roi is usually larger than levelRoi
1802 ASSERT(w == roi.Width() && h == roi.Height());
1803 const PGFRect levelRoi = ComputeLevelROI();
1804 ASSERT(roi.left <= levelRoi.left && levelRoi.right <= roi.right);
1805 ASSERT(roi.top <= levelRoi.top && levelRoi.bottom <= roi.bottom);
1806
1807 if (ROIisSupported() && (levelRoi.Width() < w || levelRoi.Height() < h)) {
1808 // ROI is used
1809 w = levelRoi.Width();
1810 h = levelRoi.Height();
1811 roiOffsetX = levelRoi.left - roi.left;
1812 roiOffsetY = levelRoi.top - roi.top;
1813 yOffset = roiOffsetX + roiOffsetY*yw;
1814
1815 if (m_downsample) {
1816 const PGFRect& downsampledRoi = GetAlignedROI(1);
1817 uOffset = levelRoi.left/2 - downsampledRoi.left + (levelRoi.top/2 - downsampledRoi.top)*m_width[1];
1818 } else {
1819 uOffset = yOffset;
1820 }
1821 }
1822 #endif
okay so this whole chunk only exists if __PGFROISUPPORT__ is defined โ it's compiled out otherwise, iykyk. Inside: roi comes from GetAlignedROI(), and the comment is very clear that it's in pixels and that roi is usually larger than levelRoi. Then we assert that w matches roi.Width() and h matches roi.Height() โ fr fr, we're checking our homework ๐. levelRoi comes from ComputeLevelROI(), and two more asserts confirm roi fully contains levelRoi on both the left/right axis and the top/bottom axis.
Then the vibe check: if (ROIisSupported() && (levelRoi.Width() < w || levelRoi.Height() < h)) โ that's the "ROI is used" branch, per the comment ๐ธ. Inside it, w and h get overwritten with levelRoi.Width() and levelRoi.Height(), the x and y offsets are computed as the difference between levelRoi and roi edges, and yOffset becomes roiOffsetX + roiOffsetY*yw.
Then the downsample tea โ: if m_downsample is truthy, we grab downsampledRoi = GetAlignedROI(1) and compute uOffset with all those halves โ levelRoi.left/2 - downsampledRoi.left + (levelRoi.top/2 - downsampledRoi.top)*m_width[1]. If not, uOffset = yOffset. Simple as that, no notes ๐
.
1824 const double dP = 1.0/h;
1825 int defMap[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; ASSERT(sizeof(defMap)/sizeof(defMap[0]) == MaxChannels);
1826 if (channelMap == nullptr) channelMap = defMap;
1827 DataT uAvg, vAvg;
1828 double percent = 0;
1829 UINT32 i, j;
dP is literally 1.0/h โ the per-row progress step, bestie, so the callback can count up nicely โจ. defMap is the identity mapping { 0, 1, 2, 3, 4, 5, 6, 7 }, and we assert its length equals MaxChannels โ if MaxChannels ever changed, this assert would riot ๐. If channelMap is nullptr, we swap in defMap. Then uAvg and vAvg (type DataT) get declared, percent starts at 0, and i and j are our loop girlies. Periodt.
1831 switch(m_header.mode) {
1832 case ImageModeBitmap:
1833 {
1834 ASSERT(m_header.channels == 1);
1835 ASSERT(m_header.bpp == 1);
1836 ASSERT(bpp == 1);
1837
1838 const UINT32 w2 = (w + 7)/8;
1839 DataT* y = m_channel[0]; ASSERT(y);
1840
1841 if (m_preHeader.version & Version7) {
1842 // new unpacked version has a little better compression ratio
1843 // since version 7
1844 for (i = 0; i < h; i++) {
1845 UINT32 cnt = 0;
1846 for (j = 0; j < w2; j++) {
1847 UINT8 byte = 0;
1848 for (int k = 0; k < 8; k++) {
1849 byte <<= 1;
1850 UINT8 bit = 0;
1851 if (cnt < w) {
1852 bit = y[yOffset + cnt] & 1;
1853 }
1854 byte |= bit;
1855 cnt++;
1856 }
1857 buff[j] = byte;
1858 }
1859 yOffset += yw;
1860 buff += pitch;
1861
1862 if (cb) {
1863 percent += dP;
1864 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
1865 }
1866 }
1867 } else {
1868 // old versions
1869 // packed pixels: 8 pixel in 1 byte of channel[0]
1870 if (!(m_preHeader.version & Version5)) yw = w2; // not version 5 or 6
1871 yOffset = roiOffsetX/8 + roiOffsetY*yw; // 1 byte in y contains 8 pixel values
1872 for (i = 0; i < h; i++) {
1873 for (j = 0; j < w2; j++) {
1874 buff[j] = Clamp8(y[yOffset + j] + YUVoffset8);
1875 }
1876 yOffset += yw;
1877 buff += pitch;
1878
1879 if (cb) {
1880 percent += dP;
1881 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
1882 }
1883 }
1884 }
1885 break;
1886 }
okay we're switching on m_header.mode and the first stop is ImageModeBitmap ๐. Three asserts up top: m_header.channels == 1, m_header.bpp == 1, and bpp == 1 โ bitmap means ONE channel, ONE bit per pixel, no negotiation ๐.
w2 = (w + 7)/8 is the byte-rounded row width (classic bit-packing math, we love her), and y points at m_channel[0] with an assert that it's not null.
Now the fork: if (m_preHeader.version & Version7) โ the comments say this is the new unpacked version and it "has a little better compression ratio" since version 7 โจ. It walks row by row, and for each of the w2 output bytes it builds a byte from 8 bits: shift left, grab bit = y[yOffset + cnt] & 1 only if cnt < w (otherwise the bit stays 0 โ that's the padding, bestie), OR it in, and cnt++ every single iteration regardless. Then buff[j] = byte;. Row done โ yOffset += yw; and buff += pitch;. And if cb exists, we bump percent += dP and call (*cb)(percent, true, data) โ and if it returns truthy, ReturnWithError(EscapePressed) ๐จ yes the user can literally bail out mid-render.
The else is the old versions path: "packed pixels: 8 pixel in 1 byte of channel[0]" ๐ธ. Note the spicy detail โ if (!(m_preHeader.version & Version5)) yw = w2; with the comment "not version 5 or 6", so only non-5-and-non-6 versions get yw reassigned. Then yOffset = roiOffsetX/8 + roiOffsetY*yw; โ comment says "1 byte in y contains 8 pixel values" โ and each row is just buff[j] = Clamp8(y[yOffset + j] + YUVoffset8);. Same progress callback, same escape hatch. Same chaotic perfection ๐
.
1887 case ImageModeIndexedColor:
1888 case ImageModeGrayScale:
1889 case ImageModeHSLColor:
1890 case ImageModeHSBColor:
1891 {
1892 ASSERT(m_header.channels >= 1);
1893 ASSERT(m_header.bpp == m_header.channels*8);
1894 ASSERT(bpp%8 == 0);
1895
1896 UINT32 cnt, channels = bpp/8; ASSERT(channels >= m_header.channels);
1897
1898 for (i=0; i < h; i++) {
1899 UINT32 yPos = yOffset;
1900 cnt = 0;
1901 for (j=0; j < w; j++) {
1902 for (UINT32 c=0; c < m_header.channels; c++) {
1903 buff[cnt + channelMap[c]] = Clamp8(m_channel[c][yPos] + YUVoffset8);
1904 }
1905 cnt += channels;
1906 yPos++;
1907 }
1908 yOffset += yw;
1909 buff += pitch;
1910
1911 if (cb) {
1912 percent += dP;
1913 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
1914 }
1915 }
1916 break;
1917 }
four cases share one body: ImageModeIndexedColor, ImageModeGrayScale, ImageModeHSLColor, ImageModeHSBColor โ they're all 8-bit-per-channel coded, so they get the same treatment ๐ซถ.
Asserts: m_header.channels >= 1, m_header.bpp == m_header.channels*8, and bpp%8 == 0. Then channels = bpp/8 with an assert that channels >= m_header.channels โ so the destination can have MORE channels than the source, which is exactly why channelMap is doing its little routing job ๐.
The loop: for each row, reset yPos = yOffset and cnt = 0. For each of the w pixels, for each source channel c, write buff[cnt + channelMap[c]] = Clamp8(m_channel[c][yPos] + YUVoffset8); โ so the channel gets placed at the position channelMap[c] says, not just straight across. Then cnt += channels (note: channels, the destination stride) and yPos++. Row wrap: yOffset += yw; buff += pitch;. Same callback, same EscapePressed bail ๐
.
1918 case ImageModeGray16:
1919 {
1920 ASSERT(m_header.channels >= 1);
1921 ASSERT(m_header.bpp == m_header.channels*16);
1922
1923 const DataT yuvOffset16 = 1 << (UsedBitsPerChannel() - 1);
1924 UINT32 cnt, channels;
1925
1926 if (bpp%16 == 0) {
1927 const int shift = 16 - UsedBitsPerChannel(); ASSERT(shift >= 0);
1928 UINT16 *buff16 = (UINT16 *)buff;
1929 int pitch16 = pitch/2;
1930 channels = bpp/16; ASSERT(channels >= m_header.channels);
1931
1932 for (i=0; i < h; i++) {
1933 UINT32 yPos = yOffset;
1934 cnt = 0;
1935 for (j=0; j < w; j++) {
1936 for (UINT32 c=0; c < m_header.channels; c++) {
1937 buff16[cnt + channelMap[c]] = Clamp16((m_channel[c][yPos] + yuvOffset16) << shift);
1938 }
1939 cnt += channels;
1940 yPos++;
1941 }
1942 yOffset += yw;
1943 buff16 += pitch16;
1944
1945 if (cb) {
1946 percent += dP;
1947 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
1948 }
1949 }
1950 } else {
1951 ASSERT(bpp%8 == 0);
1952 const int shift = __max(0, UsedBitsPerChannel() - 8);
1953 channels = bpp/8; ASSERT(channels >= m_header.channels);
1954
1955 for (i=0; i < h; i++) {
1956 UINT32 yPos = yOffset;
1957 cnt = 0;
1958 for (j=0; j < w; j++) {
1959 for (UINT32 c=0; c < m_header.channels; c++) {
1960 buff[cnt + channelMap[c]] = Clamp8((m_channel[c][yPos] + yuvOffset16) >> shift);
1961 }
1962 cnt += channels;
1963 yPos++;
1964 }
1965 yOffset += yw;
1966 buff += pitch;
1967
1968 if (cb) {
1969 percent += dP;
1970 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
1971 }
1972 }
1973 }
1974 break;
1975 }
ImageModeGray16 โ the 16-bit era, bestie ๐. Asserts: m_header.channels >= 1 and m_header.bpp == m_header.channels*16.
yuvOffset16 is 1 << (UsedBitsPerChannel() - 1) โ the midpoint of however many bits are actually used, no cap ๐. Then we split on bpp%16 == 0:
- Yes, 16-bit output:
shift = 16 - UsedBitsPerChannel()withASSERT(shift >= 0)โ if used bits exceeded 16 that assert would absolutely go off ๐.buff16reinterpretsbuffasUINT16 *,pitch16 = pitch/2(because each unit is two bytes now, slay),channels = bpp/16asserted>= m_header.channels. Then the same routing dance but withClamp16((m_channel[c][yPos] + yuvOffset16) << shift)โ shift UP into the 16-bit space โจ.cnt += channels,yPos++,yOffset += yw,buff16 += pitch16, progress callback, escape check. Chef's kiss ๐ - No, fallback:
ASSERT(bpp%8 == 0);thenshift = __max(0, UsedBitsPerChannel() - 8);โ note the__max(0, ...), so it never goes negative, that's the safety net ๐ซถ.channels = bpp/8asserted>= m_header.channels, and here it'sClamp8((m_channel[c][yPos] + yuvOffset16) >> shift)โ shift DOWN into 8-bit land. Same increments, same callback, sameEscapePressedexit. Periodt.
1976 case ImageModeRGBColor:
1977 {
1978 ASSERT(m_header.channels == 3);
1979 ASSERT(m_header.bpp == m_header.channels*8);
and we land on ImageModeRGBColor right as the excerpt cuts out ๐ญ โ but not before two asserts set the mood: m_header.channels == 3 and m_header.bpp == m_header.channels*8. Three channels, 8 bits each. The classic. We'll pick her up in the next part, bestie ๐
...and we're picking up mid-conversion, bestie, right inside the ImageModeRGB32 case. The vibes? Impeccable. The math? Also impeccable. Let's go ๐ซถ
1980 ASSERT(bpp%8 == 0);
1981 ASSERT(bpp >= m_header.bpp);
1982
1983 DataT* y = m_channel[0]; ASSERT(y);
1984 DataT* u = m_channel[1]; ASSERT(u);
1985 DataT* v = m_channel[2]; ASSERT(v);
1986 UINT8 *buffg = &buff[channelMap[1]],
1987 *buffr = &buff[channelMap[2]],
1988 *buffb = &buff[channelMap[0]];
1989 UINT8 g;
1990 UINT32 cnt, channels = bpp/8;
1991
1992 if (m_downsample) {
1993 for (i=0; i < h; i++) {
1994 UINT32 uPos = uOffset;
1995 UINT32 yPos = yOffset;
1996 cnt = 0;
1997 for (j=0; j < w; j++) {
1998 // u and v are downsampled
1999 uAvg = u[uPos];
2000 vAvg = v[uPos];
2001 // Yuv
2002 buffg[cnt] = g = Clamp8(y[yPos] + YUVoffset8 - ((uAvg + vAvg ) >> 2)); // must be logical shift operator
2003 buffr[cnt] = Clamp8(uAvg + g);
2004 buffb[cnt] = Clamp8(vAvg + g);
2005 cnt += channels;
2006 if (j & 1) uPos++;
2007 yPos++;
2008 }
2009 if (i & 1) uOffset += uw;
2010 yOffset += yw;
2011 buffb += pitch;
2012 buffg += pitch;
2013 buffr += pitch;
2014
2015 if (cb) {
2016 percent += dP;
2017 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
2018 }
2019 }
2020
2021 } else {
2022 for (i=0; i < h; i++) {
2023 cnt = 0;
2024 UINT32 yPos = yOffset;
2025 for (j = 0; j < w; j++) {
2026 uAvg = u[yPos];
2027 vAvg = v[yPos];
2028 // Yuv
2029 buffg[cnt] = g = Clamp8(y[yPos] + YUVoffset8 - ((uAvg + vAvg ) >> 2)); // must be logical shift operator
2030 buffr[cnt] = Clamp8(uAvg + g);
2031 buffb[cnt] = Clamp8(vAvg + g);
2032 cnt += channels;
2033 yPos++;
2034 }
2035 yOffset += yw;
2036 buffb += pitch;
2037 buffg += pitch;
2038 buffr += pitch;
2039
2040 if (cb) {
2041 percent += dP;
2042 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
2043 }
2044 }
2045 }
2046 break;
2047 }
okay so quick vibe check on what just happened โจ โ we grabbed the three channel pointers y, u, v out of m_channel[0..2] and asserted all three are real (no nulls allowed, periodt ๐
โโ๏ธ). Then the green/red/blue byte pointers get pointed into buff at channelMap[1], channelMap[2], and channelMap[0] respectively โ yes the mapping is gโ1, rโ2, bโ0, that's just how this file likes it, iykyk.
Then it splits on m_downsample:
- downsampled path โ
uandvget read atuPos, anduPosonly steps forwardif (j & 1), so on every other column. TheuOffsetonly advancesif (i & 1)too. That's the whole "u and v are downsampled" thing the comment is whispering about ๐ - not downsampled path โ everyone indexes with plain
yPos, no funny business, nouOffsetat all.
Both paths do the same YUVโRGB dance: g comes from y[yPos] + YUVoffset8 - ((uAvg + vAvg) >> 2) and the comment is SCREAMING at you that it must be the logical shift operator, then buffr = Clamp8(uAvg + g) and buffb = Clamp8(vAvg + g). Everything gets clamped because overflow is not the moment ๐ซ
And cnt += channels where channels = bpp/8, so we're striding across the buffer properly. Plus if there's a callback cb, we bump percent += dP every row and if it comes back truthy we ReturnWithError(EscapePressed) โ yes bestie, the user can hit escape mid-decode and we respect it ๐
Now the 48-bit glow-up ๐ โ same energy, twice the bits:
2048 case ImageModeRGB48:
2049 {
2050 ASSERT(m_header.channels == 3);
2051 ASSERT(m_header.bpp == 48);
2052
2053 const DataT yuvOffset16 = 1 << (UsedBitsPerChannel() - 1);
2054
2055 DataT* y = m_channel[0]; ASSERT(y);
2056 DataT* u = m_channel[1]; ASSERT(u);
2057 DataT* v = m_channel[2]; ASSERT(v);
2058 UINT32 cnt, channels;
2059 DataT g;
2060
2061 if (bpp >= 48 && bpp%16 == 0) {
2062 const int shift = 16 - UsedBitsPerChannel(); ASSERT(shift >= 0);
2063 UINT16 *buff16 = (UINT16 *)buff;
2064 int pitch16 = pitch/2;
2065 channels = bpp/16; ASSERT(channels >= m_header.channels);
2066
2067 for (i=0; i < h; i++) {
2068 UINT32 uPos = uOffset;
2069 UINT32 yPos = yOffset;
2070 cnt = 0;
2071 for (j=0; j < w; j++) {
2072 uAvg = u[uPos];
2073 vAvg = v[uPos];
2074 // Yuv
2075 g = y[yPos] + yuvOffset16 - ((uAvg + vAvg ) >> 2); // must be logical shift operator
2076 buff16[cnt + channelMap[1]] = Clamp16(g << shift);
2077 buff16[cnt + channelMap[2]] = Clamp16((uAvg + g) << shift);
2078 buff16[cnt + channelMap[0]] = Clamp16((vAvg + g) << shift);
2079 cnt += channels;
2080 if (!m_downsample || (j & 1)) uPos++;
2081 yPos++;
2082 }
2083 if (!m_downsample || (i & 1)) uOffset += uw;
2084 yOffset += yw;
2085 buff16 += pitch16;
2086
2087 if (cb) {
2088 percent += dP;
2089 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
2090 }
2091 }
2092 } else {
2093 ASSERT(bpp%8 == 0);
2094 const int shift = __max(0, UsedBitsPerChannel() - 8);
2095 channels = bpp/8; ASSERT(channels >= m_header.channels);
2096
2097 for (i=0; i < h; i++) {
2098 UINT32 uPos = uOffset;
2099 UINT32 yPos = yOffset;
2100 cnt = 0;
2101 for (j=0; j < w; j++) {
2102 uAvg = u[uPos];
2103 vAvg = v[uPos];
2104 // Yuv
2105 g = y[yPos] + yuvOffset16 - ((uAvg + vAvg ) >> 2); // must be logical shift operator
2106 buff[cnt + channelMap[1]] = Clamp8(g >> shift);
2107 buff[cnt + channelMap[2]] = Clamp8((uAvg + g) >> shift);
2108 buff[cnt + channelMap[0]] = Clamp8((vAvg + g) >> shift);
2109 cnt += channels;
2110 if (!m_downsample || (j & 1)) uPos++;
2111 yPos++;
2112 }
2113 if (!m_downsample || (i & 1)) uOffset += uw;
2114 yOffset += yw;
2115 buff += pitch;
2116
2117 if (cb) {
2118 percent += dP;
2119 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
2120 }
2121 }
2122 }
2123 break;
2124 }
The checks here are strict: m_header.channels == 3 and m_header.bpp == 48, no negotiating ๐ค. And the offset constant is yuvOffset16 = 1 << (UsedBitsPerChannel() - 1) โ a 16-bit-flavoured offset this time.
Then two whole branches:
bpp >= 48 && bpp%16 == 0โ we're in 16-bit land.shift = 16 - UsedBitsPerChannel()with anASSERT(shift >= 0), the buffer gets cast toUINT16 *buff16,pitch16 = pitch/2,channels = bpp/16(asserted>= m_header.channels). Values go throughClamp16after a<< shift. And notice the downsample guard flipped toif (!m_downsample || (j & 1)) uPos++;andif (!m_downsample || (i & 1)) uOffset += uw;โ so when downsampling is off everything advances every time, and when it's on it only advances on odd indices. Same soul, different syntax ๐ฆ- else โ
ASSERT(bpp%8 == 0),shift = __max(0, UsedBitsPerChannel() - 8),channels = bpp/8. Here it's back to 8-bitbuff,Clamp8, and right-shifts>> shift. Same!m_downsample || (โฆ)guards, samepitchwalk.
Both branches still do the cb progress ping and can bail with EscapePressed. Iconic behaviour, honestly.
And then ๐ Lab color enters the chat:
2125 case ImageModeLabColor:
2126 {
2127 ASSERT(m_header.channels == 3);
2128 ASSERT(m_header.bpp == m_header.channels*8);
2129 ASSERT(bpp%8 == 0);
2130
2131 DataT* l = m_channel[0]; ASSERT(l);
2132 DataT* a = m_channel[1]; ASSERT(a);
2133 DataT* b = m_channel[2]; ASSERT(b);
2134 UINT32 cnt, channels = bpp/8; ASSERT(channels >= m_header.channels);
2135
2136 for (i=0; i < h; i++) {
2137 UINT32 uPos = uOffset;
2138 UINT32 yPos = yOffset;
2139 cnt = 0;
2140 for (j=0; j < w; j++) {
2141 uAvg = a[uPos];
2142 vAvg = b[uPos];
2143 buff[cnt + channelMap[0]] = Clamp8(l[yPos] + YUVoffset8);
2144 buff[cnt + channelMap[1]] = Clamp8(uAvg + YUVoffset8);
2145 buff[cnt + channelMap[2]] = Clamp8(vAvg + YUVoffset8);
2146 cnt += channels;
2147 if (!m_downsample || (j & 1)) uPos++;
2148 yPos++;
2149 }
2150 if (!m_downsample || (i & 1)) uOffset += uw;
2151 yOffset += yw;
2152 buff += pitch;
2153
Three asserts up top: m_header.channels == 3, m_header.bpp == m_header.channels*8, and bpp%8 == 0. Then l, a, b get pulled from m_channel[0], [1], [2] โ renamed locals, same slots โ each asserted non-null. channels = bpp/8 with the >= m_header.channels assert, same as always.
No YUV math gymnastics here โ it's just adding YUVoffset8 to each of l[yPos], a[uPos], b[uPos] and clamping to 8 bits, writing them into buff at channelMap[0], [1], [2]. The !m_downsample || (j & 1) / (i & 1) advance guards are here too, yOffset += yw, buff += pitch.
...and the block keeps going, so hold that thought bestie, we're not done with this case yet ๐
okay so we are DEEP in the encoder guts now โ past the point of no return, unpacking every channel into the output buffer one row at a time. Strap in bestie, this is the pixel-pushing part. ๐ซถ
2154 if (cb) {
2155 percent += dP;
2156 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
2157 }
2158 }
2159 break;
2160 }
see that cb situation? Every so often we bump percent by dP and poke the progress callback โ and if it comes back true, we hit ReturnWithError(EscapePressed) and dip immediately. Respecting the escape key is the whole personality here. ๐
2161 case ImageModeLab48:
2162 {
2163 ASSERT(m_header.channels == 3);
2164 ASSERT(m_header.bpp == m_header.channels*16);
2165
2166 const DataT yuvOffset16 = 1 << (UsedBitsPerChannel() - 1);
2167
2168 DataT* l = m_channel[0]; ASSERT(l);
2169 DataT* a = m_channel[1]; ASSERT(a);
2170 DataT* b = m_channel[2]; ASSERT(b);
2171 UINT32 cnt, channels;
2172
2173 if (bpp%16 == 0) {
2174 const int shift = 16 - UsedBitsPerChannel(); ASSERT(shift >= 0);
2175 UINT16 *buff16 = (UINT16 *)buff;
2176 int pitch16 = pitch/2;
2177 channels = bpp/16; ASSERT(channels >= m_header.channels);
2178
2179 for (i=0; i < h; i++) {
2180 UINT32 uPos = uOffset;
2181 UINT32 yPos = yOffset;
2182 cnt = 0;
2183 for (j=0; j < w; j++) {
2184 uAvg = a[uPos];
2185 vAvg = b[uPos];
2186 buff16[cnt + channelMap[0]] = Clamp16((l[yPos] + yuvOffset16) << shift);
2187 buff16[cnt + channelMap[1]] = Clamp16((uAvg + yuvOffset16) << shift);
2188 buff16[cnt + channelMap[2]] = Clamp16((vAvg + yuvOffset16) << shift);
2189 cnt += channels;
2190 if (!m_downsample || (j & 1)) uPos++;
2191 yPos++;
2192 }
2193 if (!m_downsample || (i & 1)) uOffset += uw;
2194 yOffset += yw;
2195 buff16 += pitch16;
2196
2197 if (cb) {
2198 percent += dP;
2199 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
2200 }
2201 }
2202 } else {
2203 ASSERT(bpp%8 == 0);
2204 const int shift = __max(0, UsedBitsPerChannel() - 8);
2205 channels = bpp/8; ASSERT(channels >= m_header.channels);
2206
2207 for (i=0; i < h; i++) {
2208 UINT32 uPos = uOffset;
2209 UINT32 yPos = yOffset;
2210 cnt = 0;
2211 for (j=0; j < w; j++) {
2212 uAvg = a[uPos];
2213 vAvg = b[uPos];
2214 buff[cnt + channelMap[0]] = Clamp8((l[yPos] + yuvOffset16) >> shift);
2215 buff[cnt + channelMap[1]] = Clamp8((uAvg + yuvOffset16) >> shift);
2216 buff[cnt + channelMap[2]] = Clamp8((vAvg + yuvOffset16) >> shift);
2217 cnt += channels;
2218 if (!m_downsample || (j & 1)) uPos++;
2219 yPos++;
2220 }
2221 if (!m_downsample || (i & 1)) uOffset += uw;
2222 yOffset += yw;
2223 buff += pitch;
2224
2225 if (cb) {
2226 percent += dP;
2227 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
2228 }
2229 }
2230 }
2231 break;
2232 }
ImageModeLab48 struts in with three channels โ l, a, b โ and immediately asserts m_header.bpp == m_header.channels*16. Then it splits into two whole roads: the bpp%16 == 0 road goes wide with UINT16 *buff16, pitch16 = pitch/2 and Clamp16; the else road asserts bpp%8 == 0, works out shift = __max(0, UsedBitsPerChannel() - 8) and packs bytes with Clamp8. Two roads, one iconic output. ๐ฆ
2233 case ImageModeRGBA:
2234 case ImageModeCMYKColor:
2235 {
2236 ASSERT(m_header.channels == 4);
2237 ASSERT(m_header.bpp == m_header.channels*8);
2238 ASSERT(bpp%8 == 0);
2239
2240 DataT* y = m_channel[0]; ASSERT(y);
2241 DataT* u = m_channel[1]; ASSERT(u);
2242 DataT* v = m_channel[2]; ASSERT(v);
2243 DataT* a = m_channel[3]; ASSERT(a);
2244 UINT8 g, aAvg;
2245 UINT32 cnt, channels = bpp/8; ASSERT(channels >= m_header.channels);
2246
2247 for (i=0; i < h; i++) {
2248 UINT32 uPos = uOffset;
2249 UINT32 yPos = yOffset;
2250 cnt = 0;
2251 for (j=0; j < w; j++) {
2252 uAvg = u[uPos];
2253 vAvg = v[uPos];
2254 aAvg = Clamp8(a[uPos] + YUVoffset8);
2255 // Yuv
2256 buff[cnt + channelMap[1]] = g = Clamp8(y[yPos] + YUVoffset8 - ((uAvg + vAvg ) >> 2)); // must be logical shift operator
2257 buff[cnt + channelMap[2]] = Clamp8(uAvg + g);
2258 buff[cnt + channelMap[0]] = Clamp8(vAvg + g);
2259 buff[cnt + channelMap[3]] = aAvg;
2260 cnt += channels;
2261 if (!m_downsample || (j & 1)) uPos++;
2262 yPos++;
2263 }
2264 if (!m_downsample || (i & 1)) uOffset += uw;
2265 yOffset += yw;
2266 buff += pitch;
2267
2268 if (cb) {
2269 percent += dP;
2270 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
2271 }
2272 }
2273 break;
2274 }
ImageModeRGBA and ImageModeCMYKColor share a single case โ besties sharing a locker, honestly. Four channels, m_header.bpp == m_header.channels*8, channels = bpp/8, and there's a UINT8 g, aAvg; holding it down. Also: that // must be logical shift operator comment is right there in the source and we do NOT touch it. Not the comment, not the (uAvg + vAvg ) space before the paren โ that whitespace is canon, babes. ๐
2275 case ImageModeCMYK64:
2276 {
2277 ASSERT(m_header.channels == 4);
2278 ASSERT(m_header.bpp == 64);
2279
2280 const DataT yuvOffset16 = 1 << (UsedBitsPerChannel() - 1);
2281
2282 DataT* y = m_channel[0]; ASSERT(y);
2283 DataT* u = m_channel[1]; ASSERT(u);
2284 DataT* v = m_channel[2]; ASSERT(v);
2285 DataT* a = m_channel[3]; ASSERT(a);
2286 DataT g, aAvg;
2287 UINT32 cnt, channels;
2288
2289 if (bpp%16 == 0) {
2290 const int shift = 16 - UsedBitsPerChannel(); ASSERT(shift >= 0);
2291 UINT16 *buff16 = (UINT16 *)buff;
2292 int pitch16 = pitch/2;
2293 channels = bpp/16; ASSERT(channels >= m_header.channels);
2294
2295 for (i=0; i < h; i++) {
2296 UINT32 uPos = uOffset;
2297 UINT32 yPos = yOffset;
2298 cnt = 0;
2299 for (j=0; j < w; j++) {
2300 uAvg = u[uPos];
2301 vAvg = v[uPos];
2302 aAvg = a[uPos] + yuvOffset16;
2303 // Yuv
2304 g = y[yPos] + yuvOffset16 - ((uAvg + vAvg ) >> 2); // must be logical shift operator
2305 buff16[cnt + channelMap[1]] = Clamp16(g << shift);
2306 buff16[cnt + channelMap[2]] = Clamp16((uAvg + g) << shift);
2307 buff16[cnt + channelMap[0]] = Clamp16((vAvg + g) << shift);
2308 buff16[cnt + channelMap[3]] = Clamp16(aAvg << shift);
2309 cnt += channels;
2310 if (!m_downsample || (j & 1)) uPos++;
2311 yPos++;
2312 }
2313 if (!m_downsample || (i & 1)) uOffset += uw;
2314 yOffset += yw;
2315 buff16 += pitch16;
2316
2317 if (cb) {
2318 percent += dP;
2319 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
2320 }
2321 }
2322 } else {
2323 ASSERT(bpp%8 == 0);
2324 const int shift = __max(0, UsedBitsPerChannel() - 8);
2325 channels = bpp/8; ASSERT(channels >= m_header.channels);
2326
2327 for (i=0; i < h; i++) {
2328 UINT32 uPos = uOffset;
ImageModeCMYK64 is the last case standing โ four channels, m_header.bpp == 64, yuvOffset16 = 1 << (UsedBitsPerChannel() - 1), Clamp16, buff16 += pitch16, the whole 16-bit fit. And then the bpp%16 == 0 test fails and it slides into the else, asserts bpp%8 == 0, sets shift = __max(0, UsedBitsPerChannel() - 8)โฆ and the for (i=0; i < h; i++) loop has just opened, UINT32 uPos = uOffset; and all. ๐
and that is literally where we stop, mid-loop, mid-row, mid-vibe โ no closure, only momentum ๐โจ
okay bestie we are so deep in the guts of GetBitmap() right now, and honestly? it's giving brain surgery ๐ง โจ โ this is the bit where the actual pixels get shoved into your buffer. Picking up mid-loop in the YUVโRGBA conversion, no rewind, no recap, we just keep going:
UINT32 yPos = yOffset;
cnt = 0;
for (j=0; j < w; j++) {
uAvg = u[uPos];
vAvg = v[uPos];
aAvg = a[uPos] + yuvOffset16;
// Yuv
g = y[yPos] + yuvOffset16 - ((uAvg + vAvg ) >> 2); // must be logical shift operator
buff[cnt + channelMap[1]] = Clamp8(g >> shift);
buff[cnt + channelMap[2]] = Clamp8((uAvg + g) >> shift);
buff[cnt + channelMap[0]] = Clamp8((vAvg + g) >> shift);
buff[cnt + channelMap[3]] = Clamp8(aAvg >> shift);
cnt += channels;
if (!m_downsample || (j & 1)) uPos++;
yPos++;
}
if (!m_downsample || (i & 1)) uOffset += uw;
yOffset += yw;
buff += pitch;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
}
break;
}
Little things worth pointing at with a stick, because they're lowkey iconic:
- the line with
g = ...carries a comment that is SCREAMING at you:// must be logical shift operator. noted. do not touch. ๐ โโ๏ธ - every single channel write goes through
Clamp8, and the order ischannelMap[1],channelMap[2],channelMap[0],channelMap[3]โ so like, not hardcoded, the map decides. cnt += channels;โ the stride moves by however many channels we've got.- the chroma pointer
uPosonly moves when!m_downsampleor(j & 1)โ anduOffsetonly moves when!m_downsampleor(i & 1). yOffset += yw;andbuff += pitch;close out each row, and if there's a callbackcb, it doespercent += dP;and can bail out early by returningEscapePressedthroughReturnWithError. ๐
and then break; and the case block closes. bye bestie ๐ซถ
now, gated content alert ๐ โ the next whole mode only exists if __PGF32SUPPORT__ is defined. it's ImageModeGray32, it asserts m_header.channels == 1 and m_header.bpp == 32, and then it branches three whole ways on bpp:
#ifdef __PGF32SUPPORT__
case ImageModeGray32:
{
ASSERT(m_header.channels == 1);
ASSERT(m_header.bpp == 32);
const int yuvOffset31 = 1 << (UsedBitsPerChannel() - 1);
DataT* y = m_channel[0]; ASSERT(y);
if (bpp == 32) {
const int shift = 31 - UsedBitsPerChannel(); ASSERT(shift >= 0);
UINT32 *buff32 = (UINT32 *)buff;
int pitch32 = pitch/4;
for (i=0; i < h; i++) {
UINT32 yPos = yOffset;
for (j = 0; j < w; j++) {
buff32[j] = Clamp31((y[yPos++] + yuvOffset31) << shift);
}
yOffset += yw;
buff32 += pitch32;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
} else if (bpp == 16) {
const int usedBits = UsedBitsPerChannel();
UINT16 *buff16 = (UINT16 *)buff;
int pitch16 = pitch/2;
if (usedBits < 16) {
const int shift = 16 - usedBits;
for (i=0; i < h; i++) {
UINT32 yPos = yOffset;
for (j = 0; j < w; j++) {
buff16[j] = Clamp16((y[yPos++] + yuvOffset31) << shift);
}
yOffset += yw;
buff16 += pitch16;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
} else {
const int shift = __max(0, usedBits - 16);
for (i=0; i < h; i++) {
UINT32 yPos = yOffset;
for (j = 0; j < w; j++) {
buff16[j] = Clamp16((y[yPos++] + yuvOffset31) >> shift);
}
yOffset += yw;
buff16 += pitch16;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
}
} else {
ASSERT(bpp == 8);
const int shift = __max(0, UsedBitsPerChannel() - 8);
for (i=0; i < h; i++) {
UINT32 yPos = yOffset;
for (j = 0; j < w; j++) {
buff[j] = Clamp8((y[yPos++] + yuvOffset31) >> shift);
}
yOffset += yw;
buff += pitch;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
}
break;
}
#endif
okay so the tea โ on this one:
yuvOffset31 = 1 << (UsedBitsPerChannel() - 1);is computed once at the top, andDataT* y = m_channel[0];gets anASSERT(y)for safety. only one channel, because gray. obviously.bpp == 32:shift = 31 - UsedBitsPerChannel()withASSERT(shift >= 0)because the math has to be sane, then aUINT32 *buff32view withpitch32 = pitch/4, and every pixel isClamp31((y[yPos++] + yuvOffset31) << shift). ๐bpp == 16:usedBits = UsedBitsPerChannel(),UINT16 *buff16,pitch16 = pitch/2, and then it splits AGAIN โ ifusedBits < 16it'sshift = 16 - usedBitsand a left shift<< shift; otherwiseshift = __max(0, usedBits - 16)and a right shift>> shift. both clamp throughClamp16. no cap, it really said "two more branches". ๐ญ- anything else:
ASSERT(bpp == 8);andshift = __max(0, UsedBitsPerChannel() - 8);, writing plain bytes withClamp8. - all three paths keep the exact same progress-callback ritual โ
percent += dP;andReturnWithError(EscapePressed)ifcbsays stop.
then #endif and we're out of the gated zone. โจ
next up: ImageModeRGB12. asserts on channels == 3, m_header.bpp == m_header.channels*4, bpp == m_header.channels*4, and !m_downsample. three channel pointers, one UINT16 yval, one UINT32 cnt:
case ImageModeRGB12:
{
ASSERT(m_header.channels == 3);
ASSERT(m_header.bpp == m_header.channels*4);
ASSERT(bpp == m_header.channels*4);
ASSERT(!m_downsample);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
UINT16 yval;
UINT32 cnt;
for (i=0; i < h; i++) {
UINT32 yPos = yOffset;
cnt = 0;
for (j=0; j < w; j++) {
// Yuv
uAvg = u[yPos];
vAvg = v[yPos];
yval = Clamp4(y[yPos] + YUVoffset4 - ((uAvg + vAvg ) >> 2)); // must be logical shift operator
if (j%2 == 0) {
buff[cnt] = UINT8(Clamp4(vAvg + yval) | (yval << 4));
cnt++;
buff[cnt] = Clamp4(uAvg + yval);
} else {
buff[cnt] |= Clamp4(vAvg + yval) << 4;
cnt++;
buff[cnt] = UINT8(yval | (Clamp4(uAvg + yval) << 4));
cnt++;
}
yPos++;
}
yOffset += yw;
buff += pitch;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
break;
}
notes bestie, because this one is fancy:
Clamp4all over the place andYUVoffset4, with that same// must be logical shift operatorreminder on the>> 2.- it's packing nibbles ๐ โ on even
jit writesUINT8(Clamp4(vAvg + yval) | (yval << 4))thencnt++andbuff[cnt] = Clamp4(uAvg + yval); on oddjit doesbuff[cnt] |= Clamp4(vAvg + yval) << 4;,cnt++, thenbuff[cnt] = UINT8(yval | (Clamp4(uAvg + yval) << 4));,cnt++. yPos++is unconditional here, and there's nom_downsampleanything โ the assert already swore it off.- same callback, same
EscapePressed, samebreak;. consistency is hot. ๐
and then, right as it gets good, ImageModeRGB16. asserts channels == 3, m_header.bpp == 16, bpp == 16, !m_downsample. grab a snack ๐:
case ImageModeRGB16:
{
ASSERT(m_header.channels == 3);
ASSERT(m_header.bpp == 16);
ASSERT(bpp == 16);
ASSERT(!m_downsample);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
UINT16 yval;
UINT16 *buff16 = (UINT16 *)buff;
int pitch16 = pitch/2;
for (i=0; i < h; i++) {
UINT32 yPos = yOffset;
for (j = 0; j < w; j++) {
// Yuv
uAvg = u[yPos];
vAvg = v[yPos];
yval = Clamp6(y[yPos++] + YUVoffset6 - ((uAvg + vAvg ) >> 2)); // must be logical shift operator
buff16[j] = (yval << 5) | ((Clamp6(uAvg + yval) >> 1) << 11) | (Clamp6(vAvg + yval) >> 1);
}
yOffset += yw;
buff16 += pitch16;
and that is literally where this part cuts off ๐ญ โ mid-loop, mid-case, buff16 still moving. we pick it back up in the next one, bestie, don't go anywhere ๐ซถ
okay bestie we're literally mid-function right now, so here's the rest of that code, no interruptions ๐
2510 if (cb) {
2511 percent += dP;
2512 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
2513 }
2514 }
2515 break;
2516 }
2517 default:
2518 ASSERT(false);
2519 }
2520
2521 #ifdef _DEBUG
2522 // display ROI (RGB) in debugger
2523 roiimage.width = w;
2524 roiimage.height = h;
2525 if (pitch > 0) {
2526 roiimage.pitch = pitch;
2527 roiimage.data = buff;
2528 } else {
2529 roiimage.pitch = -pitch;
2530 roiimage.data = buff + (h - 1)*pitch;
2531 }
2532 #endif
2533
2534 }
DataT * CPGFImage::GetChannel (int c = 0) [inline]
Okay so this one hands you back an internal YUV image channel, like it's passing you a lil snack ๐
Parameters
cโ A channel index
Returns
An internal YUV image channel
Definition at line 317 of file PGFimage.h.
317 { ASSERT(c >= 0 && c < MaxChannels); return m_channel[c]; }
const RGBQUAD * CPGFImage::GetColorTable () const [inline]
Returns
Address of color table ๐จ (yes literally just the address, no drama, no cap)
Definition at line 330 of file PGFimage.h.
330 { return m_postHeader.clut; }
void CPGFImage::GetColorTable (UINT32 iFirstColor, UINT32 nColors, RGBQUAD * prgbColors) const
This one retrieves red, green, blue (RGB) color values from a range of entries in the palette of the DIB section. Heads up bestie: it might throw an IOException. It's giving "handle your exceptions, queen" ๐ญ
Parameters
iFirstColorโ The color table index of the first entry to retrieve.nColorsโ The number of color table entries to retrieve.prgbColorsโ A pointer to the array ofRGBQUADstructures to retrieve the color table entries.
Definition at line 1350 of file PGFimage.cpp.
1350 {
1351 if (iFirstColor + nColors > ColorTableLen) ReturnWithError(ColorTableError);
1352
1353 for (UINT32 i=iFirstColor, j=0; j < nColors; i++, j++) {
1354 prgbColors[j] = m_postHeader.clut[i];
1355 }
1356 }
UINT32 CPGFImage::GetEncodedHeaderLength () const
Return the length of all encoded headers in bytes. Precondition: The PGF image has been opened with a call of Open(...). So likeโฆ open it first, bestie, obviously ๐ซถ
Returns
The length of all encoded headers in bytes
Definition at line 648 of file PGFimage.cpp.
648 {
649 ASSERT(m_decoder);
650 return m_decoder->GetEncodedHeaderLength();
651 }
UINT32 CPGFImage::GetEncodedLevelLength (int level) const [inline]
Return the length of an encoded PGF level in bytes. Precondition (again, iconic recurring theme): The PGF image has been opened with a call of Open(...). โจ
Parameters
levelโ The image level
Returns
The length of a PGF level in bytes
Definition at line 367 of file PGFimage.h.
367 { ASSERT(level >= 0 && level < m_header.nLevels); return m_levelLength[m_header.nLevels - level - 1]; }
const PGFHeader * CPGFImage::GetHeader () const [inline]
Return the PGF header structure. That's it, that's the whole vibe ๐
Returns
A PGF header structure
Definition at line 335 of file PGFimage.h.
335 { return &m_header; }
UINT32 CPGFImage::GetMaxValue () const [inline]
Get maximum intensity value for image modes with more than eight bits per channel. Don't call this method before the PGF header has been read. Like, seriously, don't โ it'll be giving chaos ๐
Returns
The maximum intensity value.
Definition at line 341 of file PGFimage.h.
341 { return (1 << m_header.usedBitsPerChannel) - 1; }
const UINT8 * CPGFImage::GetUserData (UINT32 & cachedSize, UINT32 * pTotalSize = nullptr) const
Return user data and size of user data. Precondition: The PGF image has been opened with a call of Open(...). ๐ธ
Parameters
cachedSizeโ[out]Size of returned user data in bytes.pTotalSizeโ[optional out]Pointer to return the size of user data stored in image header in bytes.
Returns
A pointer to user data or nullptr if there is no user data available.
Return user data and size of user data. Precondition: The PGF image has been opened with a call of Open(...). In an encoder scenario don't call this method before WriteHeader(). ๐ซถ
Parameters
cachedSizeโ[out]Size of returned user data in bytes.pTotalSizeโ[optional out]Pointer to return the size of user data stored in image header in bytes.
Returns
A pointer to user data or nullptr if there is no user data available.
Definition at line 337 of file PGFimage.cpp.
337 {
338 cachedSize = m_postHeader.cachedUserDataLen;
339 if (pTotalSize) *pTotalSize = m_postHeader.userDataLen;
340 return m_postHeader.userData;
341 }
UINT64 CPGFImage::GetUserDataPos () const [inline]
Return the stream position of the user data or 0. Precondition: The PGF image has been opened with a call of Open(...). (yes bestie, the Open(...) lore continues, it's a whole saga ๐ฆ)
Definition at line 346 of file PGFimage.h.
346 { return m_userDataPos; }
void CPGFImage::GetYUV (int pitch, DataT * buff, BYTE bpp, int channelMap[] = nullptr, CallbackPtr cb = nullptr, void * data = nullptr) const
Get YUV image data in interleaved format: (ordering is YUV[A]). The absolute value of pitch is the number of bytes of an image row of the given image buffer. If pitch is negative, then the image buffer must point to the last row of a bottom-up image (first byte on last row). if pitch is positive, then the image buffer must point to the first row of a top-down image (first byte). The sequence of output channels in the output image buffer does not need to be the same as provided by PGF. In case of different sequences you have to provide a channelMap of size of expected channels (depending on image mode). For example, PGF provides a channel sequence BGR in RGB color mode. If your provided image buffer expects a channel sequence VUY, then the channelMap looks like { 2, 1, 0 }. It might throw an IOException. ๐ญ
Parameters
pitchโ The number of bytes of a row of the image buffer.buffโ An image buffer.bppโ The number of bits per pixel used in image buffer.channelMapโ A integer array containing the mapping of PGF channel ordering to expected channel ordering.cbโ A pointer to a callback procedure. The procedure is called after each copied buffer row. Ifcbreturnstrue, then it stops proceeding.dataโ Data Pointer to C++ class container to host callback procedure.
Get YUV image data in interleaved format: (ordering is YUV[A]). The absolute value of pitch is the number of bytes of an image row of the given image buffer. If pitch is negative, then the image buffer must point to the last row of a bottom-up image (first byte on last row). if pitch is positive, then the image buffer must point to the first row of a top-down image (first byte). The sequence of output channels in the output image buffer does not need to be the same as provided by PGF. In case of different sequences you have to provide a channelMap of size of expected channels (depending on image mode). For example, PGF provides a channel sequence BGR in RGB color mode. If your provided image buffer expects a channel sequence VUY, then the channelMap looks like { 2, 1, 0 }. It might throw an IOException. ๐
Parameters
pitchโ The number of bytes of a row of the image buffer.buffโ An image buffer.bppโ The number of bits per pixel used in image buffer.channelMapโ A integer array containing the mapping of PGF channel ordering to expected channel ordering.cbโ A pointer to a callback procedure. The procedure is called after each copied buffer row. Ifcbreturnstrue, then it stops proceeding.
Definition at line 2550 of file PGFimage.cpp.
2550 {
2551 ASSERT(buff);
2552 const UINT32 w = m_width[0];
2553 const UINT32 h = m_height[0];
2554 const bool wOdd = (1 == w%2);
2555 const int dataBits = DataTSize*8; ASSERT(dataBits == 16 || dataBits == 32);
2556 const int pitch2 = pitch/DataTSize;
2557 const int yuvOffset = (dataBits == 16) ? YUVoffset8 : YUVoffset16;
2558 const double dP = 1.0/h;
2559
2560 int defMap[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; ASSERT(sizeof(defMap)/sizeof(defMap[0]) == MaxChannels);
2561 if (channelMap == nullptr) channelMap = defMap;
2562 int sampledPos = 0, yPos = 0;
2563 DataT uAvg, vAvg;
2564 double percent = 0;
2565 UINT32 i, j;
2566
2567 if (m_header.channels == 3) {
2568 ASSERT(bpp%dataBits == 0);
2569
2570 DataT* y = m_channel[0]; ASSERT(y);
2571 DataT* u = m_channel[1]; ASSERT(u);
2572 DataT* v = m_channel[2]; ASSERT(v);
2573 int cnt, channels = bpp/dataBits; ASSERT(channels >= m_header.channels);
2574
2575 for (i=0; i < h; i++) {
2576 if (i%2) sampledPos -= (w + 1)/2;
2577 cnt = 0;
2578 for (j=0; j < w; j++) {
2579 if (m_downsample) {
2580 // image was downsampled
2581 uAvg = u[sampledPos];
2582 vAvg = v[sampledPos];
2583 } else {
2584 uAvg = u[yPos];
2585 vAvg = v[yPos];
2586 }
2587 buff[cnt + channelMap[0]] = y[yPos];
2588 buff[cnt + channelMap[1]] = uAvg;
2589 buff[cnt + channelMap[2]] = vAvg;
2590 yPos++;
2591 cnt += channels;
2592 if (j%2) sampledPos++;
2593 }
2594 buff += pitch2;
2595 if (wOdd) sampledPos++;
2596
2597 if (cb) {
2598 percent += dP;
2599 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
2600 }
2601 }
2602 } else if (m_header.channels == 4) {
2603 ASSERT(m_header.bpp == m_header.channels*8);
2604 ASSERT(bpp%dataBits == 0);
2605
2606 DataT* y = m_channel[0]; ASSERT(y);
2607 DataT* u = m_channel[1]; ASSERT(u);
2608 DataT* v = m_channel[2]; ASSERT(v);
2609 DataT* a = m_channel[3]; ASSERT(a);
2610 UINT8 aAvg;
2611 int cnt, channels = bpp/dataBits; ASSERT(channels >= m_header.channels);
2612
2613 for (i=0; i < h; i++) {
2614 if (i%2) sampledPos -= (w + 1)/2;
2615 cnt = 0;
2616 for (j=0; j < w; j++) {
2617 if (m_downsample) {
2618 // image was downsampled
2619 uAvg = u[sampledPos];
2620 vAvg = v[sampledPos];
2621 aAvg = Clamp8(a[sampledPos] + yuvOffset);
2622 } else {
2623 uAvg = u[yPos];
2624 vAvg = v[yPos];
2625 aAvg = Clamp8(a[yPos] + yuvOffset);
2626 }
2627 // Yuv
2628 buff[cnt + channelMap[0]] = y[yPos];
2629 buff[cnt + channelMap[1]] = uAvg;
2630 buff[cnt + channelMap[2]] = vAvg;
2631 buff[cnt + channelMap[3]] = aAvg;
2632 yPos++;
2633 cnt += channels;
2634 if (j%2) sampledPos++;
2635 }
2636 buff += pitch2;
2637 if (wOdd) sampledPos++;
2638
2639 if (cb) {
2640 percent += dP;
2641 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
2642 }
2643 }
2644 }
2645 }
๐ธ UINT32 CPGFImage::Height (int level = 0) const [inline] ๐ธ
Bestie, this one gives you the image height of channel 0 at whatever level you're asking about, measured in pixels. And the tea here? ๐ The height it hands back is completely independent of any Read-operations and any ROI. So like, it doesn't care what you've been reading or cropping โ it just tells you the truth. ๐
Parameters
- level โ a level
Returns
- the image level height, in pixels
Definition at line 420 of file PGFimage.h.
420 { ASSERT(level >= 0); return LevelSizeL(m_header.height, level); }
๐ฆ void CPGFImage::ImportBitmap (int pitch, UINT8 * buff, BYTE bpp, int channelMap[] = nullptr, CallbackPtr cb = nullptr, void * data = nullptr) ๐ฆ
Okay so this is the move where you pull in an image from a specific image buffer. โจ Normally you call this before Write(...) and after SetHeader(...) โ that's the order, don't fight it.
The absolute value of pitch is the number of bytes of an image row. Here's the vibe check:
- If
pitchis negative, thenbuffpoints to the last row of a bottom-up image (first byte on the last row). - If
pitchis positive, thenbuffpoints to the first row of a top-down image (first byte).
Now, the sequence of input channels in your input image buffer does not need to match what PGF expects. Fr fr. If the sequences are different you have to hand over a channelMap that's the size of the expected channels (which depends on the image mode). Example time ๐
โ PGF expects, in RGB color mode, a channel sequence of BGR. So if your image buffer has the channel sequence ARGB, then your channelMap looks like { 3, 2, 1, 0 }. Iconic.
Also heads up: it might throw an IOException. ๐ญ
Parameters
- pitch โ the number of bytes of a row of the image buffer.
- buff โ an image buffer.
- bpp โ the number of bits per pixel used in the image buffer.
- channelMap โ an integer array holding the mapping of input channel ordering to the expected channel ordering.
- cb โ a pointer to a callback procedure. The procedure gets called after each imported buffer row. If
cbreturns true, then it stops proceeding. ๐ - data โ data pointer to a C++ class container to host the callback procedure.
Definition at line 792 of file PGFimage.cpp.
792 {
793 ASSERT(buff);
794 ASSERT(m_channel[0]);
795
796 // color transform
797 RgbToYuv(pitch, buff, bpp, channelMap, cb, data);
798
799 if (m_downsample) {
800 // Subsampling of the chrominance and alpha channels
801 for (int i=1; i < m_header.channels; i++) {
802 Downsample(i);
803 }
804 }
805 }
๐ bool CPGFImage::ImportIsSupported (BYTE mode) [static] ๐
This is the vibe check for whether an import image mode is even valid. It's static, so it's just sitting there ready to tell you yes or no. โจ
Parameters
- mode โ the image mode
Returns
trueif an image of the given mode can be imported withImportBitmap(...)
Definition at line 1305 of file PGFimage.cpp.
1305 {
1306 size_t size = DataTSize;
1307
1308 if (size >= 2) {
1309 switch(mode) {
1310 case ImageModeBitmap:
1311 case ImageModeIndexedColor:
1312 case ImageModeGrayScale:
1313 case ImageModeRGBColor:
1314 case ImageModeCMYKColor:
1315 case ImageModeHSLColor:
1316 case ImageModeHSBColor:
1317 //case ImageModeDuotone:
1318 case ImageModeLabColor:
1319 case ImageModeRGB12:
1320 case ImageModeRGB16:
1321 case ImageModeRGBA:
1322 return true;
1323 }
1324 }
1325 if (size >= 3) {
1326 switch(mode) {
1327 case ImageModeGray16:
1328 case ImageModeRGB48:
1329 case ImageModeLab48:
1330 case ImageModeCMYK64:
1331 //case ImageModeDuotone16:
1332 return true;
1333 }
1334 }
1335 if (size >=4) {
1336 switch(mode) {
1337 case ImageModeGray32:
1338 return true;
1339 }
1340 }
1341 return false;
1342 }
Spotted it? ๐ Those//case ImageModeDuotone:and//case ImageModeDuotone16:lines are commented out in the source โ they are not supported here. Don't come for me, I'm just the messenger. ๐
๐ง void CPGFImage::ImportYUV (int pitch, DataT * buff, BYTE bpp, int channelMap[] = nullptr, CallbackPtr cb = nullptr, void * data = nullptr) ๐ง
Now we're importing a YUV image from a specified image buffer. Same energy as before, bestie:
The absolute value of pitch is the number of bytes of an image row, and:
- If
pitchis negative, thenbuffpoints to the last row of a bottom-up image (first byte on the last row). - If
pitchis positive, thenbuffpoints to the first row of a top-down image (first byte).
The sequence of input channels in the input image buffer doesn't have to match what PGF expects โ same deal as before. If the sequences differ, you provide a channelMap of the size of the expected channels (depends on the image mode). Example, since examples are everything ๐
: PGF expects, in RGB color mode, a channel sequence of BGR. If your provided image buffer holds the channel sequence VUY, then the channelMap looks like { 2, 1, 0 }.
And yes, it might throw an IOException. ๐ญ
Parameters
- pitch โ the number of bytes of a row of the image buffer.
- buff โ an image buffer.
- bpp โ the number of bits per pixel used in the image buffer.
- channelMap โ an integer array containing the mapping of input channel ordering to the expected channel ordering.
- cb โ a pointer to a callback procedure. The procedure is called after each imported buffer row. If
cbreturns true, then it stops proceeding. - data โ data pointer to a C++ class container to host the callback procedure.
It might throw an IOException. Bestie, it's giving "handle your exceptions or perish". ๐
Parameters
pitchโ how many bytes one row of the image buffer eats up.buffโ the image buffer itself, the whole vibe.bppโ bits per pixel used in that image buffer.channelMapโ an integer array holding the mapping from your input channel ordering to the expected channel ordering.cbโ a pointer to a callback procedure. It gets called after each imported buffer row, and ifcbreturnstrue, the whole thing stops proceeding. Like, it taps out. โ
Definition at line 2661 of file PGFimage.cpp.
2661 {
2662 ASSERT(buff);
2663 const double dP = 1.0/m_header.height;
2664 const int dataBits = DataTSize*8; ASSERT(dataBits == 16 || dataBits == 32);
2665 const int pitch2 = pitch/DataTSize;
2666 const int yuvOffset = (dataBits == 16) ? YUVoffset8 : YUVoffset16;
2667
2668 int yPos = 0, cnt = 0;
2669 double percent = 0;
2670 int defMap[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; ASSERT(sizeof(defMap)/sizeof(defMap[0]) == MaxChannels);
2671
2672 if (channelMap == nullptr) channelMap = defMap;
2673
2674 if (m_header.channels == 3) {
2675 ASSERT(bpp%dataBits == 0);
2676
2677 DataT* y = m_channel[0]; ASSERT(y);
2678 DataT* u = m_channel[1]; ASSERT(u);
2679 DataT* v = m_channel[2]; ASSERT(v);
2680 const int channels = bpp/dataBits; ASSERT(channels >= m_header.channels);
2681
2682 for (UINT32 h=0; h < m_header.height; h++) {
2683 if (cb) {
2684 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
2685 percent += dP;
2686 }
2687
2688 cnt = 0;
2689 for (UINT32 w=0; w < m_header.width; w++) {
2690 y[yPos] = buff[cnt + channelMap[0]];
2691 u[yPos] = buff[cnt + channelMap[1]];
2692 v[yPos] = buff[cnt + channelMap[2]];
2693 yPos++;
2694 cnt += channels;
2695 }
2696 buff += pitch2;
2697 }
2698 } else if (m_header.channels == 4) {
2699 ASSERT(bpp%dataBits == 0);
2700
2701 DataT* y = m_channel[0]; ASSERT(y);
2702 DataT* u = m_channel[1]; ASSERT(u);
2703 DataT* v = m_channel[2]; ASSERT(v);
2704 DataT* a = m_channel[3]; ASSERT(a);
2705 const int channels = bpp/dataBits; ASSERT(channels >= m_header.channels);
2706
2707 for (UINT32 h=0; h < m_header.height; h++) {
2708 if (cb) {
2709 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
2710 percent += dP;
2711 }
2712
2713 cnt = 0;
2714 for (UINT32 w=0; w < m_header.width; w++) {
2715 y[yPos] = buff[cnt + channelMap[0]];
2716 u[yPos] = buff[cnt + channelMap[1]];
2717 v[yPos] = buff[cnt + channelMap[2]];
2718 a[yPos] = buff[cnt + channelMap[3]] - yuvOffset;
2719 yPos++;
2720 cnt += channels;
2721 }
2722 buff += pitch2;
2723 }
2724 }
2725
2726 if (m_downsample) {
2727 // Subsampling of the chrominance and alpha channels
2728 for (int i=1; i < m_header.channels; i++) {
2729 Downsample(i);
2730 }
2731 }
2732 }
CPGFImage::Init () [private]
Definition at line 69 of file PGFimage.cpp.
69 {
70 // init pointers
71 m_decoder = nullptr;
72 m_encoder = nullptr;
73 m_levelLength = nullptr;
74
75 // init members
76 #ifdef __PGFROISUPPORT__
77 m_streamReinitialized = false;
78 #endif
79 m_currentLevel = 0;
80 m_quant = 0;
81 m_userDataPos = 0;
82 m_downsample = false;
83 m_favorSpeedOverSize = false;
84 m_useOMPinEncoder = true;
85 m_useOMPinDecoder = true;
86 m_cb = nullptr;
87 m_cbArg = nullptr;
88 m_progressMode = PM_Relative;
89 m_percent = 0;
90 m_userDataPolicy = UP_CacheAll;
91
92 // init preHeader
93 memcpy(m_preHeader.magic, PGFMagic, 3);
94 m_preHeader.version = PGFVersion;
95 m_preHeader.hSize = 0;
96
97 // init postHeader
98 m_postHeader.userData = nullptr;
99 m_postHeader.userDataLen = 0;
100 m_postHeader.cachedUserDataLen = 0;
101
102 // init channels
103 for (int i = 0; i < MaxChannels; i++) {
104 m_channel[i] = nullptr;
105 m_wtChannel[i] = nullptr;
106 }
107
108 // set image width and height
109 for (int i = 0; i < MaxChannels; i++) {
110 m_width[0] = 0;
111 m_height[0] = 0;
112 }
113 }
bool CPGFImage::IsFullyRead () const [inline]
Returns true if all levels have been read. That's it, that's the whole function, she's an icon of brevity. ๐
Definition at line 436 of file PGFimage.h.
436 { return m_currentLevel == 0; }
bool CPGFImage::IsOpen () const [inline]
Returns true if the PGF has been opened for reading. Simple. Clean. Slays. โจ
Definition at line 77 of file PGFimage.h.
77 { return m_decoder != nullptr; }
BYTE CPGFImage::Level () const [inline]
Returns the current image level. Since Read(...) can be used to read each image level separately, it's genuinely helpful to know which level you're currently standing in, bestie. And plot twist: the current level immediately after Open(...) is Levels(). ๐
Returns
Current image level
Definition at line 427 of file PGFimage.h.
427 { return (BYTE)m_currentLevel; }
BYTE CPGFImage::Levels () const [inline]
Returns the number of image levels. Just the count. The headcount. ๐
Returns
Number of image levels
Definition at line 432 of file PGFimage.h.
432 { return m_header.nLevels; }
UINT32 CPGFImage::LevelSizeH (UINT32 size, int level) [inline], [static]
Computes and returns the image width/height of the HH subband at a given level. High-pass math, low-key gorgeous. ๐ฆ
Parameters
sizeโ the original image size (like, the width or height at level 0).levelโ an image level.
Returns
high pass size at given level in pixels
Definition at line 506 of file PGFimage.h.
506 { ASSERT(level >= 0); UINT32 d = 1 << (level - 1); return (size + d - 1) >> level; }
UINT32 CPGFImage::LevelSizeL (UINT32 size, int level) [inline], [static]
Computes and returns the image width/height of the LL subband at a given level. Same energy, different band. ๐
Parameters
sizeโ the original image size (e.g. width or height at level 0).levelโ an image level.
Returns
Image width/height at given level in pixels
Definition at line 499 of file PGFimage.h.
499 { ASSERT(level >= 0); UINT32 d = 1 << level; return (size + d - 1) >> level; }
BYTE CPGFImage::MaxChannelDepth (BYTE version = PGFVersion) [inline], [static]
Returns the maximum channel depth. Yes, version defaults to PGFVersion, we love a default. ๐ซถ
Parameters
versionโ pgf pre-header version number.
Returns
maximum channel depth in bit of given version (16 or 32 bit)
Definition at line 518 of file PGFimage.h.
518 { return (version & PGF32) ? 32 : 16; }
BYTE CPGFImage::Mode () const [inline]
Returns the image mode. An image mode is a predefined constant value (peep PGFtypes.h, iykyk) compatible with Adobe Photoshop, and it represents an image type and format. Basically the image's whole identity. ๐
Returns
Image mode
Definition at line 455 of file PGFimage.h.
455 { return m_header.mode; }
void CPGFImage::Open (CPGFStream * stream)
Opens a PGF image at the current stream position: reads the pre-header, the header, and checks the image type. Precondition, and hear me out: the stream has been opened for reading. If not, that's on you, bestie. It might throw an IOException. ๐ญ
Parameters
streamโ a PGF stream.
๐ source drop: Definition at line 141 of file PGFimage.cpp. โ buckle up bestie, this one is doing the ENTIRE opening routine and she's not even out of breath ๐
141 {
142 ASSERT(stream);
143
144 // create decoder and read PGFPreHeader PGFHeader PGFPostHeader LevelLengths
145 m_decoder = new CDecoder(stream, m_preHeader, m_header, m_postHeader, m_levelLength,
146 m_userDataPos, m_useOMPinDecoder, m_userDataPolicy);
147
148 if (m_header.nLevels > MaxLevel) ReturnWithError(FormatCannotRead);
149
150 // set current level
151 m_currentLevel = m_header.nLevels;
152
153 // set image width and height
154 m_width[0] = m_header.width;
155 m_height[0] = m_header.height;
156
157 // complete header
158 if (!CompleteHeader()) ReturnWithError(FormatCannotRead);
159
160 // interpret quant parameter
161 if (m_header.quality > DownsampleThreshold &&
162 (m_header.mode == ImageModeRGBColor ||
163 m_header.mode == ImageModeRGBA ||
164 m_header.mode == ImageModeRGB48 ||
165 m_header.mode == ImageModeCMYKColor ||
166 m_header.mode == ImageModeCMYK64 ||
167 m_header.mode == ImageModeLabColor ||
168 m_header.mode == ImageModeLab48)) {
169 m_downsample = true;
170 m_quant = m_header.quality - 1;
171 } else {
172 m_downsample = false;
173 m_quant = m_header.quality;
174 }
175
176 // set channel dimensions (chrominance is subsampled by factor 2)
177 if (m_downsample) {
178 for (int i=1; i < m_header.channels; i++) {
179 m_width[i] = (m_width[0] + 1) >> 1;
180 m_height[i] = (m_height[0] + 1) >> 1;
181 }
182 } else {
183 for (int i=1; i < m_header.channels; i++) {
184 m_width[i] = m_width[0];
185 m_height[i] = m_height[0];
186 }
187 }
188
189 if (m_header.nLevels > 0) {
190 // init wavelet subbands
191 for (int i=0; i < m_header.channels; i++) {
192 m_wtChannel[i] = new CWaveletTransform(m_width[i], m_height[i], m_header.nLevels);
193 }
194
195 // used in Read when PM_Absolute
196 m_percent = pow(0.25, m_header.nLevels);
197
198 } else {
199 // very small image: we don't use DWT and encoding
200
201 // read channels
202 for (int c=0; c < m_header.channels; c++) {
203 const UINT32 size = m_width[c]*m_height[c];
204 m_channel[c] = new(std::nothrow) DataT[size];
205 if (!m_channel[c]) ReturnWithError(InsufficientMemory);
206
207 // read channel data from stream
208 for (UINT32 i=0; i < size; i++) {
209 int count = DataTSize;
210 stream->Read(&count, &m_channel[c][i]);
211 if (count != DataTSize) ReturnWithError(MissingData);
212 }
213 }
214 }
215 }
BYTE CPGFImage::Quality () const [inline] โ the quality check ๐
okay so this one is literally the shortest queen in the whole file and I'm obsessed. She just hands you back the PGF quality. That quality lives inbetween 0 and MaxQuality. And bestie, listen โ PGF quality 0 means lossless quality. Yes, ZERO is the lossless one. Don't let that trip you up, it's giving reverse psychology ๐ญ๐
Returns: PGF quality
๐ defined at line 442 of PGFimage.h
442 { return m_header.quality; }
void CPGFImage::Read (int level = 0, CallbackPtr cb = nullptr, void * data = nullptr) โ read & decode some levels ๐ธ
okay so this is the main character Read. She reads and decodes some levels of a PGF image at the current stream position. โจ
A PGF image is structured in levels, numbered between 0 and Levels() - 1. Each level can be seen as a single image, containing the same content as all other levels, but in a different size (width, height). The image size at level i is double the size (width, height) of the image at level i+1. The image at level 0 contains the original size. ๐
โ ๏ธ Precondition, bestie: the PGF image has been opened with a call ofOpen(...). It might throw anIOException. Don't say I didn't warn you โ it's literally in the source.
Parameters:
levelโ[0, nLevels)โ the image level of the resulting image in the internal image buffer.cbโ a pointer to a callback procedure. The procedure is called after reading a single level. Ifcbreturns true, then it stops proceeding.dataโ Data Pointer to C++ class container to host callback procedure.
๐ defined at line 402 of PGFimage.cpp
402 {
403 ASSERT((level >= 0 && level < m_header.nLevels) || m_header.nLevels == 0); // m_header.nLevels == 0: image didn't use wavelet transform
404 ASSERT(m_decoder);
405
406 #ifdef __PGFROISUPPORT__
407 if (ROIisSupported() && m_header.nLevels > 0) {
408 // new encoding scheme supporting ROI
409 PGFRect rect(0, 0, m_header.width, m_header.height);
410 Read(rect, level, cb, data);
411 return;
412 }
413 #endif
414
415 if (m_header.nLevels == 0) {
416 if (level == 0) {
417 // the data has already been read during open
418 // now update progress
419 if (cb) {
420 if ((*cb)(1.0, true, data)) ReturnWithError(EscapePressed);
421 }
422 }
423 } else {
424 const int levelDiff = m_currentLevel - level;
425 double percent = (m_progressMode == PM_Relative) ? pow(0.25, levelDiff) : m_percent;
426
427 // encoding scheme without ROI
428 while (m_currentLevel > level) {
429 for (int i=0; i < m_header.channels; i++) {
430 CWaveletTransform* wtChannel = m_wtChannel[i];
431 ASSERT(wtChannel);
432
433 // decode file and write stream to m_wtChannel
434 if (m_currentLevel == m_header.nLevels) {
435 // last level also has LL band
436 wtChannel->GetSubband(m_currentLevel, LL)->PlaceTile(*m_decoder, m_quant);
437 }
438 if (m_preHeader.version & Version5) {
439 // since version 5
440 wtChannel->GetSubband(m_currentLevel, HL)->PlaceTile(*m_decoder, m_quant);
441 wtChannel->GetSubband(m_currentLevel, LH)->PlaceTile(*m_decoder, m_quant);
442 } else {
443 // until version 4
444 m_decoder->DecodeInterleaved(wtChannel, m_currentLevel, m_quant);
445 }
446 wtChannel->GetSubband(m_currentLevel, HH)->PlaceTile(*m_decoder, m_quant);
447 }
448
449 volatile OSError error = NoError; // volatile prevents optimizations
450 #ifdef LIBPGF_USE_OPENMP
451 #pragma omp parallel for default(shared)
452 #endif
453 for (int i=0; i < m_header.channels; i++) {
454 // inverse transform from m_wtChannel to m_channel
455 if (error == NoError) {
456 OSError err = m_wtChannel[i]->InverseTransform(m_currentLevel, &m_width[i], &m_height[i], &m_channel[i]);
457 if (err != NoError) error = err;
458 }
459 ASSERT(m_channel[i]);
460 }
461 if (error != NoError) ReturnWithError(error);
462
463 // set new level: must be done before refresh callback
464 m_currentLevel--;
465
466 // now we have to refresh the display
467 if (m_cb) m_cb(m_cbArg);
468
469 // now update progress
470 if (cb) {
471 percent *= 4;
472 if (m_progressMode == PM_Absolute) m_percent = percent;
473 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
474 }
475 }
476 }
477 }
void CPGFImage::Read (PGFRect & rect, int level = 0, CallbackPtr cb = nullptr, void * data = nullptr) โ ROI reading ๐
Read a rectangular region of interest of a PGF image at current stream position. The origin of the coordinate axis is the top-left corner of the image. All coordinates are measured in pixels. It might throw an IOException. ๐ฆ
Parameters:
rect[inout]โ Rectangular region of interest (ROI) at level 0. The rect might be cropped.levelโ[0, nLevels)โ The image level of the resulting image in the internal image buffer.cbโ A pointer to a callback procedure. The procedure is called after reading a single level. Ifcbreturns true, then it stops proceeding.dataโ Data Pointer to C++ class container to host callback procedure.
okay and here's the encore, bestie โ the same queen saying it again but this time with extra "and decode" energy, and with the level range quietly dropped (yes, I noticed, and I'm keeping it EXACTLY as written, no cap ๐ญ):
Read and decode rectangular region of interest (ROI) of a PGF image at current stream position. The origin of the coordinate axis is the top-left corner of the image. All coordinates are measured in pixels. It might throw an IOException. ๐
Parameters:
rect[inout]โ Rectangular region of interest (ROI) at level 0. The rect might be cropped.levelโ The image level of the resulting image in the internal image buffer.cbโ A pointer to a callback procedure. The procedure is called after reading a single level. Ifcbreturns true, then it stops proceeding.dataโ Data Pointer to C++ class container to host callback procedure.
Definition at line 489 of file PGFimage.cpp.
489 {
490 ASSERT((level >= 0 && level < m_header.nLevels) || m_header.nLevels == 0); // m_header.nLevels == 0: image didn't use wavelet transform
491 ASSERT(m_decoder);
492
493 if (m_header.nLevels == 0 || !ROIisSupported()) {
494 rect.left = rect.top = 0;
495 rect.right = m_header.width; rect.bottom = m_header.height;
496 Read(level, cb, data);
497 } else {
498 ASSERT(ROIisSupported());
499 // new encoding scheme supporting ROI
500 ASSERT(rect.left < m_header.width && rect.top < m_header.height);
501
502 // check rectangle
503 if (rect.right == 0 || rect.right > m_header.width) rect.right = m_header.width;
504 if (rect.bottom == 0 || rect.bottom > m_header.height) rect.bottom = m_header.height;
505
506 const int levelDiff = m_currentLevel - level;
507 double percent = (m_progressMode == PM_Relative) ? pow(0.25, levelDiff) : m_percent;
508
509 // check level difference
510 if (levelDiff <= 0) {
511 // it is a new read call, probably with a new ROI
512 m_currentLevel = m_header.nLevels;
513 m_decoder->SetStreamPosToData();
514 }
515
516 // enable ROI decoding and reading
517 SetROI(rect);
518
519 while (m_currentLevel > level) {
520 for (int i=0; i < m_header.channels; i++) {
521 CWaveletTransform* wtChannel = m_wtChannel[i];
522 ASSERT(wtChannel);
523
524 // get number of tiles and tile indices
525 const UINT32 nTiles = wtChannel->GetNofTiles(m_currentLevel); // independent of ROI
526
527 // decode file and write stream to m_wtChannel
528 if (m_currentLevel == m_header.nLevels) { // last level also has LL band
529 ASSERT(nTiles == 1);
530 m_decoder->GetNextMacroBlock();
531 wtChannel->GetSubband(m_currentLevel, LL)->PlaceTile(*m_decoder, m_quant);
532 }
533 for (UINT32 tileY=0; tileY < nTiles; tileY++) {
534 for (UINT32 tileX=0; tileX < nTiles; tileX++) {
535 // check relevance of tile
536 if (wtChannel->TileIsRelevant(m_currentLevel, tileX, tileY)) {
537 m_decoder->GetNextMacroBlock();
538 wtChannel->GetSubband(m_currentLevel, HL)->PlaceTile(*m_decoder, m_quant, true, tileX, tileY);
539 wtChannel->GetSubband(m_currentLevel, LH)->PlaceTile(*m_decoder, m_quant, true, tileX, tileY);
540 wtChannel->GetSubband(m_currentLevel, HH)->PlaceTile(*m_decoder, m_quant, true, tileX, tileY);
541 } else {
542 // skip tile
543 m_decoder->SkipTileBuffer();
544 }
545 }
546 }
547 }
548
549 volatile OSError error = NoError; // volatile prevents optimizations
550 #ifdef LIBPGF_USE_OPENMP
551 #pragma omp parallel for default(shared)
552 #endif
553 for (int i=0; i < m_header.channels; i++) {
554 // inverse transform from m_wtChannel to m_channel
555 if (error == NoError) {
556 OSError err = m_wtChannel[i]->InverseTransform(m_currentLevel, &m_width[i], &m_height[i], &m_channel[i]);
557 if (err != NoError) error = err;
558 }
559 ASSERT(m_channel[i]);
560 }
561 if (error != NoError) ReturnWithError(error);
562
563 // set new level: must be done before refresh callback
564 m_currentLevel--;
565
566 // now we have to refresh the display
567 if (m_cb) m_cb(m_cbArg);
568
569 // now update progress
570 if (cb) {
571 percent *= 4;
572 if (m_progressMode == PM_Absolute) m_percent = percent;
573 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
574 }
575 }
576 }
577 }
okay bestie, that whole thing is the ROI-aware reading path ๐ and it is a lot, I know. But look โ it's doing the most in the cutest way: if the image has no wavelet levels (m_header.nLevels == 0) or ROI just isn't supported, it yeets the rect to the full image and hands off to Read(level, cb, data) like a good little delegate. Otherwise? Bestie, it's tiling, it's inverse-transforming, it's calling the refresh callback (m_cb), it's ticking the progress callback (cb) and bailing with EscapePressed if you say so. ๐ธ
UINT32 CPGFImage::ReadEncodedData(int level, UINT8 *target, UINT32 targetLen) const ๐
Reads the data of an encoded PGF level and copies it to a target buffer without decoding. No decoding, no drama, just straight-up byte handoff. โจ
Precondition, and I'm serious: the PGF image has been opened with a call ofOpen(...). Also it might throw anIOException. ๐ญ
Parameters
levelโ the image level ๐targetโ the target buffer ๐ซถtargetLenโ the length of the target buffer in bytes ๐
Returns
The number of bytes copied to the target buffer. ๐ฅ
Definition at line 707 of file PGFimage.cpp.
707 {
708 ASSERT(level >= 0 && level < m_header.nLevels);
709 ASSERT(target);
710 ASSERT(targetLen > 0);
711 ASSERT(m_decoder);
712
713 // reset stream position
714 m_decoder->SetStreamPosToData();
715
716 // position stream
717 UINT64 offset = 0;
718
719 for (int i=m_header.nLevels - 1; i > level; i--) {
720 offset += m_levelLength[m_header.nLevels - 1 - i];
721 }
722 m_decoder->Skip(offset);
723
724 // compute number of bytes to read
725 UINT32 len = __min(targetLen, GetEncodedLevelLength(level));
726
727 // read data
728 len = m_decoder->ReadEncodedData(target, len);
729 ASSERT(len >= 0 && len <= targetLen);
730
731 return len;
732 }
ngl the way it resets the stream to data, then walks the levels to build up offset and Skips past everything it doesn't want... it's giving precision. ๐ฏ And then __min(targetLen, GetEncodedLevelLength(level)) so you never get more than your buffer can hold. Main character behavior, honestly. ๐
UINT32 CPGFImage::ReadEncodedHeader(UINT8 *target, UINT32 targetLen) const ๐ฆ
Reads the encoded PGF header and copies it to a target buffer. The header, bestie! The little intro card of the whole file. ๐
Precondition again, same energy: the PGF image has been opened with a call ofOpen(...). And yes, it might throw anIOException. ๐
Parameters
targetโ the target buffer ๐ธtargetLenโ the length of the target buffer in bytes ๐
Returns
The number of bytes copied to the target buffer. โญ
Definition at line 660 of file PGFimage.cpp.
660 {
661 ASSERT(target);
662 ASSERT(targetLen > 0);
663 ASSERT(m_decoder);
664
665 // reset stream position
666 m_decoder->SetStreamPosToStart();
667
668 // compute number of bytes to read
669 UINT32 len = __min(targetLen, GetEncodedHeaderLength());
670
671 // read data
672 len = m_decoder->ReadEncodedData(target, len);
673 ASSERT(len >= 0 && len <= targetLen);
674
675 return len;
676 }
Note it's SetStreamPosToStart() here, not SetStreamPosToData() โ because the header lives at the start, obviously, and it's lowkey the one time we're allowed to be at the very beginning. ๐ Same __min energy with GetEncodedHeaderLength(). Chef's kiss. ๐
void CPGFImage::ReadPreview() [inline] ๐
Read and decode smallest level of a PGF image at current stream position. For details, please refert to Read(...) โ (yes, that's the original's typo, I'm keeping it, it's part of the lore now ๐ซถ). Precondition: The PGF image has been opened with a call of Open(...). It might throw an IOException.
Definition at line 111 of file PGFimage.h.
111 { Read(Levels() - 1); }
the way it's literally a one-liner. Read(Levels() - 1). That's it. That's the function. Ate and left no crumbs. ๐
void CPGFImage::Reconstruct(int level = 0) ๐
After you've written a PGF image, you can call this method followed by GetBitmap/GetYUV to get a quick reconstruction (coded -> decoded image). It might throw an IOException. ๐ฅ
Parameters
levelโ The image level of the resulting image in the internal image buffer.
โฆand here's the rest of that function's body, bestie โ literally the code, no notes ๐
348 {
349 if (m_header.nLevels == 0) {
350 // image didn't use wavelet transform
351 if (level == 0) {
352 for (int i=0; i < m_header.channels; i++) {
353 ASSERT(m_wtChannel[i]);
354 m_channel[i] = m_wtChannel[i]->GetSubband(0, LL)->GetBuffer();
355 }
356 }
357 } else {
358 int currentLevel = m_header.nLevels;
359
360 #ifdef __PGFROISUPPORT__
361 if (ROIisSupported()) {
362 // enable ROI reading
363 SetROI(PGFRect(0, 0, m_header.width, m_header.height));
364 }
365 #endif
366
367 while (currentLevel > level) {
368 for (int i=0; i < m_header.channels; i++) {
369 ASSERT(m_wtChannel[i]);
370 // dequantize subbands
371 if (currentLevel == m_header.nLevels) {
372 // last level also has LL band
373 m_wtChannel[i]->GetSubband(currentLevel, LL)->Dequantize(m_quant);
374 }
375 m_wtChannel[i]->GetSubband(currentLevel, HL)->Dequantize(m_quant);
376 m_wtChannel[i]->GetSubband(currentLevel, LH)->Dequantize(m_quant);
377 m_wtChannel[i]->GetSubband(currentLevel, HH)->Dequantize(m_quant);
378
379 // inverse transform from m_wtChannel to m_channel
380 OSError err = m_wtChannel[i]->InverseTransform(currentLevel, &m_width[i], &m_height[i], &m_channel[i]);
381 if (err != NoError) ReturnWithError(err);
382 ASSERT(m_channel[i]);
383 }
384
385 currentLevel--;
386 }
387 }
388 }
void CPGFImage::ResetStreamPos (bool startOfData)
Okay so this one is your little rewind button ๐ It resets the stream position โ either to the start of the PGF pre-header or to the start of the data. Pick your poison, girlie.
Important, no cap: you must not call this beforeOpen()or beforeWrite(). Like, genuinely not. It's giving "read the instructions first" energy ๐ญ
Use this method after Read() if you wanna read the same image several times โ for example, reading different ROIs. Same pic, multiple passes, no drama ๐
Parameters
startOfDataโtrue: you want to read the same image several times.false: resets stream position to the initial position.
Definition at line 682 of file PGFimage.cpp.
682 {
683 m_currentLevel = 0;
684 if (startOfData) {
685 ASSERT(m_decoder);
686 m_decoder->SetStreamPosToData();
687 } else {
688 if (m_decoder) {
689 m_decoder->SetStreamPosToStart();
690 } else if (m_encoder) {
691 m_encoder->SetStreamPosToStart();
692 } else {
693 ASSERT(false);
694 }
695 }
696 }
Not me explaining all this with zero shame ๐ there's still more coming, so don't go anywhere โจ
void CPGFImage::RgbToYuv (int pitch, UINT8 * rgbBuff, BYTE bpp, int channelMap[], CallbackPtr cb, void * data) [private] ๐ซ
Definition at line 1389 of file PGFimage.cpp.
okay bestie so this one is private, meaning it's the backstage crew โ you're not calling it yourself, it's just doing the vibes internally. It takes an RGB buffer and turns it into the YUV situation the rest of the class is obsessed with. Here's the whole thing, every line, no cuts:
1389 {
1390 ASSERT(buff);
1391 UINT32 yPos = 0, cnt = 0;
1392 double percent = 0;
1393 const double dP = 1.0/m_header.height;
1394 int defMap[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; ASSERT(sizeof(defMap)/sizeof(defMap[0]) == MaxChannels);
1395
1396 if (channelMap == nullptr) channelMap = defMap;
1397
1398 switch(m_header.mode) {
1399 case ImageModeBitmap:
1400 {
1401 ASSERT(m_header.channels == 1);
1402 ASSERT(m_header.bpp == 1);
1403 ASSERT(bpp == 1);
1404
1405 const UINT32 w = m_header.width;
1406 const UINT32 w2 = (m_header.width + 7)/8;
1407 DataT* y = m_channel[0]; ASSERT(y);
1408
1409 // new unpacked version since version 7
1410 for (UINT32 h = 0; h < m_header.height; h++) {
1411 if (cb) {
1412 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
1413 percent += dP;
1414 }
1415 cnt = 0;
1416 for (UINT32 j = 0; j < w2; j++) {
1417 UINT8 byte = buff[j];
1418 for (int k = 0; k < 8; k++) {
1419 UINT8 bit = (byte & 0x80) >> 7;
1420 if (cnt < w) y[yPos++] = bit;
1421 byte <<= 1;
1422 cnt++;
1423 }
1424 }
1425 buff += pitch;
1426 }
1427 /* old version: packed values: 8 pixels in 1 byte
1428 for (UINT32 h = 0; h < m_header.height; h++) {
1429 if (cb) {
1430 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
1431 percent += dP;
1432 }
1433
1434 for (UINT32 j = 0; j < w2; j++) {
1435 y[yPos++] = buff[j] - YUVoffset8;
1436 }
1437 // version 5 and 6
1438 // for (UINT32 j = w2; j < w; j++) {
1439 // y[yPos++] = YUVoffset8;
1440 //}
1441 buff += pitch;
1442 }
1443 */
1444 }
1445 break;
1446 case ImageModeIndexedColor:
1447 case ImageModeGrayScale:
1448 case ImageModeHSLColor:
1449 case ImageModeHSBColor:
1450 case ImageModeLabColor:
1451 {
1452 ASSERT(m_header.channels >= 1);
1453 ASSERT(m_header.bpp == m_header.channels*8);
1454 ASSERT(bpp%8 == 0);
1455 const int channels = bpp/8; ASSERT(channels >= m_header.channels);
1456
1457 for (UINT32 h=0; h < m_header.height; h++) {
1458 if (cb) {
1459 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
1460 percent += dP;
1461 }
1462
1463 cnt = 0;
1464 for (UINT32 w=0; w < m_header.width; w++) {
1465 for (int c=0; c < m_header.channels; c++) {
1466 m_channel[c][yPos] = buff[cnt + channelMap[c]] - YUVoffset8;
1467 }
1468 cnt += channels;
1469 yPos++;
1470 }
1471 buff += pitch;
1472 }
1473 }
1474 break;
1475 case ImageModeGray16:
1476 case ImageModeLab48:
1477 {
1478 ASSERT(m_header.channels >= 1);
1479 ASSERT(m_header.bpp == m_header.channels*16);
1480 ASSERT(bpp%16 == 0);
1481
1482 UINT16 *buff16 = (UINT16 *)buff;
1483 const int pitch16 = pitch/2;
1484 const int channels = bpp/16; ASSERT(channels >= m_header.channels);
1485 const int shift = 16 - UsedBitsPerChannel(); ASSERT(shift >= 0);
1486 const DataT yuvOffset16 = 1 << (UsedBitsPerChannel() - 1);
1487
1488 for (UINT32 h=0; h < m_header.height; h++) {
1489 if (cb) {
1490 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
1491 percent += dP;
1492 }
1493
1494 cnt = 0;
1495 for (UINT32 w=0; w < m_header.width; w++) {
1496 for (int c=0; c < m_header.channels; c++) {
1497 m_channel[c][yPos] = (buff16[cnt + channelMap[c]] >> shift) - yuvOffset16;
1498 }
1499 cnt += channels;
1500 yPos++;
1501 }
1502 buff16 += pitch16;
1503 }
1504 }
1505 break;
1506 case ImageModeRGBColor:
1507 {
1508 ASSERT(m_header.channels == 3);
1509 ASSERT(m_header.bpp == m_header.channels*8);
1510 ASSERT(bpp%8 == 0);
1511
1512 DataT* y = m_channel[0]; ASSERT(y);
1513 DataT* u = m_channel[1]; ASSERT(u);
1514 DataT* v = m_channel[2]; ASSERT(v);
1515 const int channels = bpp/8; ASSERT(channels >= m_header.channels);
1516 UINT8 b, g, r;
1517
1518 for (UINT32 h=0; h < m_header.height; h++) {
1519 if (cb) {
1520 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
1521 percent += dP;
1522 }
1523
1524 cnt = 0;
1525 for (UINT32 w=0; w < m_header.width; w++) {
1526 b = buff[cnt + channelMap[0]];
1527 g = buff[cnt + channelMap[1]];
1528 r = buff[cnt + channelMap[2]];
1529 // Yuv
1530 y[yPos] = ((b + (g << 1) + r) >> 2) - YUVoffset8;
1531 u[yPos] = r - g;
1532 v[yPos] = b - g;
1533 yPos++;
1534 cnt += channels;
1535 }
1536 buff += pitch;
1537 }
1538 }
1539 break;
1540 case ImageModeRGB48:
1541 {
1542 ASSERT(m_header.channels == 3);
1543 ASSERT(m_header.bpp == m_header.channels*16);
1544 ASSERT(bpp%16 == 0);
1545
1546 UINT16 *buff16 = (UINT16 *)buff;
1547 const int pitch16 = pitch/2;
1548 const int channels = bpp/16; ASSERT(channels >= m_header.channels);
1549 const int shift = 16 - UsedBitsPerChannel(); ASSERT(shift >= 0);
1550 const DataT yuvOffset16 = 1 << (UsedBitsPerChannel() - 1);
1551
1552 DataT* y = m_channel[0]; ASSERT(y);
1553 DataT* u = m_channel[1]; ASSERT(u);
1554 DataT* v = m_channel[2]; ASSERT(v);
1555 UINT16 b, g, r;
1556
1557 for (UINT32 h=0; h < m_header.height; h++) {
1558 if (cb) {
1559 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
1560 percent += dP;
1561 }
1562
1563 cnt = 0;
1564 for (UINT32 w=0; w < m_header.width; w++) {
1565 b = buff16[cnt + channelMap[0]] >> shift;
1566 g = buff16[cnt + channelMap[1]] >> shift;
1567 r = buff16[cnt + channelMap[2]] >> shift;
1568 // Yuv
1569 y[yPos] = ((b + (g << 1) + r) >> 2) - yuvOffset16;
1570 u[yPos] = r - g;
1571 v[yPos] = b - g;
1572 yPos++;
1573 cnt += channels;
1574 }
1575 buff16 += pitch16;
1576 }
1577 }
1578 break;
1579 case ImageModeRGBA:
1580 case ImageModeCMYKColor:
1581 {
1582 ASSERT(m_header.channels == 4);
so like, let's vibe check what's actually happening here bestie ๐
first thing: ASSERT(buff); โ no null buffers allowed, periodt. Then it sets up yPos = 0, cnt = 0, percent = 0, and const double dP = 1.0/m_header.height; โ that's the little progress step so the callback can tell you how far along we are. ๐ซถ
then: int defMap[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; with an ASSERT(sizeof(defMap)/sizeof(defMap[0]) == MaxChannels); โ so that default channel map is exactly MaxChannels long, no cap. And if you passed in nullptr for channelMap, it just gets swapped for defMap:
if (channelMap == nullptr) channelMap = defMap;
then switch(m_header.mode) and we're off, one case at a time ๐
ImageModeBitmapโ assertsm_header.channels == 1,m_header.bpp == 1, andbpp == 1. Grabsw = m_header.width,w2 = (m_header.width + 7)/8, andDataT* y = m_channel[0]. The comment literally says// new unpacked version since version 7โ so every byte gets pulled apart into 8 bits, one at a time,(byte & 0x80) >> 7, and only written whilecnt < w. Thenbuff += pitch.
also, there's a whole commented-out/* old version: packed values: 8 pixels in 1 byte */block right there in the source, and it stays in the code โ plus a// version 5 and 6bit inside it that's commented out too. Not me leaving the receipts in the file ๐ญ
ImageModeIndexedColor,ImageModeGrayScale,ImageModeHSLColor,ImageModeHSBColor,ImageModeLabColorโ all sharing one case. Assertsm_header.channels >= 1,m_header.bpp == m_header.channels*8,bpp%8 == 0, andchannels = bpp/8has to be>= m_header.channels. Then per pixel per channel:m_channel[c][yPos] = buff[cnt + channelMap[c]] - YUVoffset8;โ and yes,cnt += channels;andyPos++;every single pixel,buff += pitch;every single row.ImageModeGray16andImageModeLab48โ the 16-bit girlies. Assertsm_header.channels >= 1,m_header.bpp == m_header.channels*16,bpp%16 == 0. SetsUINT16 *buff16 = (UINT16 *)buff;,pitch16 = pitch/2,channels = bpp/16,shift = 16 - UsedBitsPerChannel(), andyuvOffset16 = 1 << (UsedBitsPerChannel() - 1);. Then:m_channel[c][yPos] = (buff16[cnt + channelMap[c]] >> shift) - yuvOffset16;โbuff16 += pitch16;per row.ImageModeRGBColorโ assertsm_header.channels == 3,m_header.bpp == m_header.channels*8,bpp%8 == 0. Grabsy,u,vout ofm_channel[0],[1],[2], with anASSERTon each. Pullsb,g,rthroughchannelMap[0],[1],[2], and does the actual math:
// Yuv
y[yPos] = ((b + (g << 1) + r) >> 2) - YUVoffset8;
u[yPos] = r - g;
v[yPos] = b - g;
then yPos++;, cnt += channels;, buff += pitch;.
ImageModeRGB48โ same energy but 16-bit. Assertsm_header.channels == 3,m_header.bpp == m_header.channels*16,bpp%16 == 0.buff16,pitch16 = pitch/2,channels = bpp/16,shift = 16 - UsedBitsPerChannel(),yuvOffset16 = 1 << (UsedBitsPerChannel() - 1);, andy,u,vall asserted.b,g,rget shifted down with>> shift, then:
// Yuv
y[yPos] = ((b + (g << 1) + r) >> 2) - yuvOffset16;
u[yPos] = r - g;
v[yPos] = b - g;
and buff16 += pitch16; per row.
ImageModeRGBAandImageModeCMYKColorโ shared case, and it opens withASSERT(m_header.channels == 4);before we even get anywhere elseโฆ
and also, every single one of those row loops does this first:
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
so if you handed over a cb and it says "stop" (returns true), it bails out with EscapePressed โ fr fr, it just leaves โ otherwise percent creeps up by dP each row. ๐
โฆand we're still inside the giant switch, bestie ๐ so here's the rest of the function body, exactly as it lives, no edits:
ASSERT(m_header.bpp == m_header.channels*8);
ASSERT(bpp%8 == 0);
const int channels = bpp/8; ASSERT(channels >= m_header.channels);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
DataT* a = m_channel[3]; ASSERT(a);
UINT8 b, g, r;
for (UINT32 h=0; h < m_header.height; h++) {
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
cnt = 0;
for (UINT32 w=0; w < m_header.width; w++) {
b = buff[cnt + channelMap[0]];
g = buff[cnt + channelMap[1]];
r = buff[cnt + channelMap[2]];
// Yuv
y[yPos] = ((b + (g << 1) + r) >> 2) - YUVoffset8;
u[yPos] = r - g;
v[yPos] = b - g;
a[yPos++] = buff[cnt + channelMap[3]] - YUVoffset8;
cnt += channels;
}
buff += pitch;
}
}
break;
case ImageModeCMYK64:
{
ASSERT(m_header.channels == 4);
ASSERT(m_header.bpp == m_header.channels*16);
ASSERT(bpp%16 == 0);
UINT16 *buff16 = (UINT16 *)buff;
const int pitch16 = pitch/2;
const int channels = bpp/16; ASSERT(channels >= m_header.channels);
const int shift = 16 - UsedBitsPerChannel(); ASSERT(shift >= 0);
const DataT yuvOffset16 = 1 << (UsedBitsPerChannel() - 1);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
DataT* a = m_channel[3]; ASSERT(a);
UINT16 b, g, r;
for (UINT32 h=0; h < m_header.height; h++) {
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
cnt = 0;
for (UINT32 w=0; w < m_header.width; w++) {
b = buff16[cnt + channelMap[0]] >> shift;
g = buff16[cnt + channelMap[1]] >> shift;
r = buff16[cnt + channelMap[2]] >> shift;
// Yuv
y[yPos] = ((b + (g << 1) + r) >> 2) - yuvOffset16;
u[yPos] = r - g;
v[yPos] = b - g;
a[yPos++] = (buff16[cnt + channelMap[3]] >> shift) - yuvOffset16;
cnt += channels;
}
buff16 += pitch16;
}
}
break;
#ifdef __PGF32SUPPORT__
case ImageModeGray32:
{
ASSERT(m_header.channels == 1);
ASSERT(m_header.bpp == 32);
ASSERT(bpp == 32);
ASSERT(DataTSize == sizeof(UINT32));
DataT* y = m_channel[0]; ASSERT(y);
UINT32 *buff32 = (UINT32 *)buff;
const int pitch32 = pitch/4;
const int shift = 31 - UsedBitsPerChannel(); ASSERT(shift >= 0);
const DataT yuvOffset31 = 1 << (UsedBitsPerChannel() - 1);
for (UINT32 h=0; h < m_header.height; h++) {
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
for (UINT32 w=0; w < m_header.width; w++) {
y[yPos++] = (buff32[w] >> shift) - yuvOffset31;
}
buff32 += pitch32;
}
}
break;
#endif
case ImageModeRGB12:
{
ASSERT(m_header.channels == 3);
ASSERT(m_header.bpp == m_header.channels*4);
ASSERT(bpp == m_header.channels*4);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
UINT8 rgb = 0, b, g, r;
for (UINT32 h=0; h < m_header.height; h++) {
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
cnt = 0;
for (UINT32 w=0; w < m_header.width; w++) {
if (w%2 == 0) {
// even pixel position
rgb = buff[cnt];
b = rgb & 0x0F;
g = (rgb & 0xF0) >> 4;
cnt++;
rgb = buff[cnt];
r = rgb & 0x0F;
} else {
// odd pixel position
b = (rgb & 0xF0) >> 4;
cnt++;
rgb = buff[cnt];
g = rgb & 0x0F;
r = (rgb & 0xF0) >> 4;
cnt++;
}
// Yuv
y[yPos] = ((b + (g << 1) + r) >> 2) - YUVoffset4;
u[yPos] = r - g;
v[yPos] = b - g;
yPos++;
}
buff += pitch;
}
}
break;
case ImageModeRGB16:
{
ASSERT(m_header.channels == 3);
ASSERT(m_header.bpp == 16);
ASSERT(bpp == 16);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
UINT16 *buff16 = (UINT16 *)buff;
UINT16 rgb, b, g, r;
const int pitch16 = pitch/2;
for (UINT32 h=0; h < m_header.height; h++) {
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
for (UINT32 w=0; w < m_header.width; w++) {
rgb = buff16[w];
r = (rgb & 0xF800) >> 10; // highest 5 bits
g = (rgb & 0x07E0) >> 5; // middle 6 bits
b = (rgb & 0x001F) << 1; // lowest 5 bits
// Yuv
y[yPos] = ((b + (g << 1) + r) >> 2) - YUVoffset6;
u[yPos] = r - g;
v[yPos] = b - g;
yPos++;
}
buff16 += pitch16;
}
}
break;
default:
ASSERT(false);
}
}
It's giving switch-statement-finale energy ๐ฌ โ every mode got its own little glow-up moment, and if nothing matches, ASSERT(false) says absolutely not, bestie, we do not do unknown image modes here.
๐ฆ bool CPGFImage::ROIisSupported () const [inline]
Vibe check: does this pgf image do Region Of Interest (ROI) or not? Returns true if the pgf image supports Region Of Interest (ROI).
Returns
true if the pgf image supports ROI.
Definition at line 466 of file PGFimage.h.
{ return (m_preHeader.version & PGFROI) == PGFROI; }
literally just a bitwise AND and a flex, iconic ๐
๐ void CPGFImage::SetChannel (DataT * channel, int c = 0) [inline]
Set internal PGF image buffer channel. you're handing the image a channel directly, bestie ๐ซถ
Parameters
channelโ A YUV data channelcโ A channel index
Definition at line 272 of file PGFimage.h.
272 { ASSERT(c >= 0 && c < MaxChannels); m_channel[c] = channel; }
void CPGFImage::SetColorTable (UINT32 iFirstColor, UINT32 nColors, const RGBQUAD * prgbColors) ๐จ
Sets the red, green, blue (RGB) color values for a range of entries in the palette (clut). It might throw an IOException. bestie, watch for that.
Parameters
iFirstColorโ The color table index of the first entry to set.nColorsโ The number of color table entries to set.prgbColorsโ A pointer to the array ofRGBQUADstructures to set the color table entries.
Definition at line 1364 of file PGFimage.cpp.
1364 {
1365 if (iFirstColor + nColors > ColorTableLen) ReturnWithError(ColorTableError);
1366
1367 for (UINT32 i=iFirstColor, j=0; j < nColors; i++, j++) {
1368 m_postHeader.clut[i] = prgbColors[j];
1369 }
1370 }
void CPGFImage::SetHeader (const PGFHeader & header, BYTE flags = 0, const UINT8 * userData = 0, UINT32 userDataLength = 0) ๐
Set PGF header and user data. Precondition: The PGF image has been never opened with Open(...). It might throw an IOException.
Parameters
headerโ A valid and already filled in PGF header structureflagsโ A combination of additional version flags. In case you use level-wise encoding then setflag = PGFROI.userDataโ A user-defined memory block containing any kind of cached metadata.userDataLengthโ The size of user-defined memory block in bytes
Definition at line 894 of file PGFimage.cpp.
894 {
895 ASSERT(!m_decoder); // current image must be closed
896 ASSERT(header.quality <= MaxQuality);
897 ASSERT(userDataLength <= MaxUserDataSize);
898
899 // init state
900 #ifdef __PGFROISUPPORT__
901 m_streamReinitialized = false;
902 #endif
903
904 // init preHeader
905 memcpy(m_preHeader.magic, PGFMagic, 3);
906 m_preHeader.version = PGFVersion | flags;
907 m_preHeader.hSize = HeaderSize;
908
909 // copy header
910 memcpy(&m_header, &header, HeaderSize);
911
912 // check quality
913 if (m_header.quality > MaxQuality) m_header.quality = MaxQuality;
914
915 // complete header
916 CompleteHeader();
917
918 // check and set number of levels
919 ComputeLevels();
920
921 // check for downsample
922 if (m_header.quality > DownsampleThreshold && (m_header.mode == ImageModeRGBColor ||
923 m_header.mode == ImageModeRGBA ||
924 m_header.mode == ImageModeRGB48 ||
925 m_header.mode == ImageModeCMYKColor ||
926 m_header.mode == ImageModeCMYK64 ||
927 m_header.mode == ImageModeLabColor ||
928 m_header.mode == ImageModeLab48)) {
929 m_downsample = true;
930 m_quant = m_header.quality - 1;
931 } else {
932 m_downsample = false;
933 m_quant = m_header.quality;
934 }
935
936 // update header size and copy user data
937 if (m_header.mode == ImageModeIndexedColor) {
938 // update header size
939 m_preHeader.hSize += ColorTableSize;
940 }
941 if (userDataLength && userData) {
942 if (userDataLength > MaxUserDataSize) userDataLength = MaxUserDataSize;
943 m_postHeader.userData = new(std::nothrow) UINT8[userDataLength];
944 if (!m_postHeader.userData) ReturnWithError(InsufficientMemory);
945 m_postHeader.userDataLen = m_postHeader.cachedUserDataLen = userDataLength;
946 memcpy(m_postHeader.userData, userData, userDataLength);
947 // update header size
948 m_preHeader.hSize += userDataLength;
949 }
950
951 // allocate channels
952 for (int i=0; i < m_header.channels; i++) {
953 // set current width and height
954 m_width[i] = m_header.width;
955 m_height[i] = m_header.height;
956
957 // allocate channels
958 ASSERT(!m_channel[i]);
959 m_channel[i] = new(std::nothrow) DataT[m_header.width*m_header.height];
960 if (!m_channel[i]) {
961 if (i) i--;
962 while(i) {
963 delete[] m_channel[i]; m_channel[i] = 0;
964 i--;
965 }
966 ReturnWithError(InsufficientMemory);
967 }
968 }
969 }
void CPGFImage::SetMaxValue (UINT32 maxValue) ๐ข
Set maximum intensity value for image modes with more than eight bits per channel. Call this method after SetHeader, but before ImportBitmap. order matters, no cap.
Parameters
maxValueโ The maximum intensity value.
Definition at line 738 of file PGFimage.cpp.
738 {
739 const BYTE bpc = m_header.bpp/m_header.channels;
740 BYTE pot = 0;
741
742 while(maxValue > 0) {
743 pot++;
744 maxValue >>= 1;
745 }
746 // store bits per channel
747 if (pot > bpc) pot = bpc;
748 if (pot > 31) pot = 31;
749 m_header.usedBitsPerChannel = pot;
750 }
void CPGFImage::SetProgressMode (ProgressMode pm) [inline] ๐
Set progress mode used in Read and Write. Default mode is PM_Relative. This method must be called before Open() or SetHeader().
PM_Relative: 100% = level difference between current level and target level ofRead/WritePM_Absolute: 100% = number of levels
Definition at line 296 of file PGFimage.h.
296 { m_progressMode = pm; }
void CPGFImage::SetRefreshCallback (RefreshCB callback, void * arg) [inline] ๐ฆ
Set refresh callback procedure and its parameter. The refresh callback is called during Read(...) after each level read. it's giving "notify me when it's ready" energy
Parameters
callbackโ A refresh callback procedureargโ A parameter of the refresh callback procedure
Definition at line 303 of file PGFimage.h.
303 { m_cb = callback; m_cbArg = arg; }
void CPGFImage::SetROI (PGFRect rect) [private] ๐
Compute ROIs for each channel and each level <= current level Called inside of Read(rect, ...).
Parameters
rectโ rectangular region of interest (ROI) at level 0
Definition at line 615 of file PGFimage.cpp.
615 {
616 ASSERT(m_decoder);
617 ASSERT(ROIisSupported());
618 ASSERT(m_wtChannel[0]);
619
620 // store ROI for a later call of GetBitmap
621 m_roi = rect;
622
623 // enable ROI decoding
624 m_decoder->SetROI();
625
626 // prepare wavelet channels for using ROI
627 m_wtChannel[0]->SetROI(rect);
628
629 if (m_downsample && m_header.channels > 1) {
630 // all further channels are downsampled, therefore downsample ROI
631 rect.left >>= 1;
632 rect.top >>= 1;
633 rect.right = (rect.right + 1) >> 1;
634 rect.bottom = (rect.bottom + 1) >> 1;
635 }
636 for (int i=1; i < m_header.channels; i++) {
637 ASSERT(m_wtChannel[i]);
638 m_wtChannel[i]->SetROI(rect);
639 }
640 }
UINT32 CPGFImage::UpdatePostHeaderSize () [private] ๐งฎ
Definition at line 1124 of file PGFimage.cpp.
1124 {
1125 ASSERT(m_encoder);
1126
1127 INT64 offset = m_encoder->ComputeOffset(); ASSERT(offset >= 0);
1128
1129 if (offset > 0) {
1130 // update post-header size and rewrite pre-header
1131 m_preHeader.hSize += (UINT32)offset;
1132 m_encoder->UpdatePostHeaderSize(m_preHeader);
1133 }
1134
1135 // write dummy levelLength into stream
1136 return m_encoder->WriteLevelLength(m_levelLength);
1137 }
BYTE CPGFImage::UsedBitsPerChannel () const โจ
Returns number of used bits per input/output image channel. Precondition: header must be initialized.
Returns
number of used bits per input/output image channel.
Definition at line 756 of file PGFimage.cpp.
756 {
757 const BYTE bpc = m_header.bpp/m_header.channels;
758
759 if (bpc > 8) {
760 return m_header.usedBitsPerChannel;
761 } else {
762 return bpc;
763 }
764 }
BYTE CPGFImage::Version () const [inline] ๐ท๏ธ
Returns the used codec major version of a pgf image
Returns
PGF codec major version of this image
Definition at line 484 of file PGFimage.h.
484 { BYTE ver = CodecMajorVersion(m_preHeader.version); return (ver <= 7) ? ver : (BYTE)m_header.version.major; }
UINT32 CPGFImage::Width (int level = 0) const [inline] ๐
Return image width of channel 0 at given level in pixels. The returned width is independent of any Read-operations and ROI. so don't stress, it's not gonna ghost you ๐
Parameters
levelโ A level
Returns
Image level width in pixels
Definition at line 413 of file PGFimage.h.
413 { ASSERT(level >= 0); return LevelSizeL(m_header.width, level); }
void CPGFImage::Write (CPGFStream * stream, UINT32 * nWrittenBytes = nullptr, CallbackPtr cb = nullptr, void * data = nullptr)
okay bestie, this one is the whole entire image writer. It encodes and writes an entire PGF image โ header AND image โ at the current stream position. โจ
A PGF image is structured in levels, numbered between 0 and Levels() - 1. Each level can be seen as a single image, containing the same content as all other levels, but in a different size (width, height). The image size at level i is double the size (width, height) of the image at level i+1. The image at level 0 contains the original size. ๐
Precondition: the PGF image contains a valid header (see alsoSetHeader(...)). It might throw anIOException.
Parameters ๐ธ
streamโ A PGF streamnWrittenBytesโ [in-out] The number of bytes written intostreamare added to the input value.cbโ A pointer to a callback procedure. The procedure is called after writing a single level. Ifcbreturnstrue, then it stops proceeding.dataโ Data Pointer to C++ class container to host callback procedure.
Definition at line 1221 of file PGFimage.cpp.
1221 {
1222 ASSERT(stream);
1223 ASSERT(m_preHeader.hSize);
1224
1225 // create wavelet transform channels and encoder
1226 UINT32 nBytes = WriteHeader(stream);
1227
1228 // write image
1229 nBytes += WriteImage(stream, cb, data);
1230
1231 // return written bytes
1232 if (nWrittenBytes) *nWrittenBytes += nBytes;
1233 }
UINT32 CPGFImage::Write (int level, CallbackPtr cb = nullptr, void * data = nullptr)
okay so this overload is the down-to-a-specific-level girlie. It encodes and writes down to the given level at the current stream position. Same level lore as above: numbered between 0 and Levels() - 1, each level same content but different size (width, height), level i is double the size of i+1, and level 0 is the original size. ๐ฆ
Preconditions: the PGF image contains a valid header (see alsoSetHeader(...)) andWriteHeader()has been called before.Levels() > 0. The ROI encoding scheme must be used (see alsoSetHeader(...)). It might throw anIOException.
Parameters ๐
levelโ [0, nLevels) The image level of the resulting image in the internal image buffer.cbโ A pointer to a callback procedure. The procedure is called after writing a single level. Ifcbreturnstrue, then it stops proceeding.dataโ Data Pointer to C++ class container to host callback procedure.
Returns ๐ The number of bytes written into stream.
Definition at line 1250 of file PGFimage.cpp.
1250 {
1251 ASSERT(m_header.nLevels > 0);
1252 ASSERT(0 <= level && level < m_header.nLevels);
1253 ASSERT(m_encoder);
1254 ASSERT(ROIisSupported());
1255
1256 const int levelDiff = m_currentLevel - level;
1257 double percent = (m_progressMode == PM_Relative) ? pow(0.25, levelDiff) : m_percent;
1258 UINT32 nWrittenBytes = 0;
1259
1260 if (m_currentLevel == m_header.nLevels) {
1261 // update post-header size, rewrite pre-header, and write dummy levelLength
1262 nWrittenBytes = UpdatePostHeaderSize();
1263 } else {
1264 // prepare for next level: save current file position, because the stream might have been reinitialized
1265 if (m_encoder->ComputeBufferLength()) {
1266 m_streamReinitialized = true;
1267 }
1268 }
1269
1270 // encoding scheme with ROI
1271 while (m_currentLevel > level) {
1272 WriteLevel(); // decrements m_currentLevel
1273
1274 if (m_levelLength) {
1275 nWrittenBytes += m_levelLength[m_header.nLevels - m_currentLevel - 1];
1276 }
1277
1278 // now update progress
1279 if (cb) {
1280 percent *= 4;
1281 if (m_progressMode == PM_Absolute) m_percent = percent;
1282 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
1283 }
1284 }
1285
1286 // automatically closing
1287 if (m_currentLevel == 0) {
1288 if (!m_streamReinitialized) {
1289 // don't write level lengths, if the stream position changed inbetween two Write operations
1290 m_encoder->UpdateLevelLength();
1291 }
1292 // delete encoder
1293 delete m_encoder; m_encoder = nullptr;
1294 }
1295
1296 return nWrittenBytes;
1297 }
UINT32 CPGFImage::WriteHeader (CPGFStream * stream)
okay so the docs literally say this twice, and honestly? iconic behavior. We're keeping both because every detail matters, periodt.
First version: Create wavelet transform channels and encoder. Write header at current stream position. Call this method before your first call of Write(int level) or WriteImage(), but after SetHeader(). This method is called inside of Write(stream, ...). It might throw an IOException.
Parameters ๐
streamโ A PGF stream
Returns โญ The number of bytes written into stream.
Second version (yes, again, but with extra FWT tea): Create wavelet transform channels and encoder. Write header at current stream position. Performs forward FWT. Call this method before your first call of Write(int level) or WriteImage(), but after SetHeader(). This method is called inside of Write(stream, ...). It might throw an IOException.
Parameters ๐ฆ
streamโ A PGF stream
Returns ๐ The number of bytes written into stream.
Definition at line 979 of file PGFimage.cpp.
979 {
980 ASSERT(m_header.nLevels <= MaxLevel);
981 ASSERT(m_header.quality <= MaxQuality); // quality is already initialized
982
983 if (m_header.nLevels > 0) {
984 volatile OSError error = NoError; // volatile prevents optimizations
985 // create new wt channels
986 #ifdef LIBPGF_USE_OPENMP
987 #pragma omp parallel for default(shared)
988 #endif
989 for (int i=0; i < m_header.channels; i++) {
990 DataT *temp = nullptr;
991 if (error == NoError) {
992 if (m_wtChannel[i]) {
993 ASSERT(m_channel[i]);
994 // copy m_channel to temp
995 int size = m_height[i]*m_width[i];
996 temp = new(std::nothrow) DataT[size];
997 if (temp) {
998 memcpy(temp, m_channel[i], size*DataTSize);
999 delete m_wtChannel[i]; // also deletes m_channel
1000 m_channel[i] = nullptr;
1001 } else {
1002 error = InsufficientMemory;
1003 }
1004 }
1005 if (error == NoError) {
1006 if (temp) {
1007 ASSERT(!m_channel[i]);
1008 m_channel[i] = temp;
1009 }
1010 m_wtChannel[i] = new CWaveletTransform(m_width[i], m_height[i], m_header.nLevels, m_channel[i]);
1011 if (m_wtChannel[i]) {
1012 #ifdef __PGFROISUPPORT__
1013 m_wtChannel[i]->SetROI(PGFRect(0, 0, m_width[i], m_height[i]));
1014 #endif
1015
1016 // wavelet subband decomposition
1017 for (int l=0; error == NoError && l < m_header.nLevels; l++) {
1018 OSError err = m_wtChannel[i]->ForwardTransform(l, m_quant);
1019 if (err != NoError) error = err;
1020 }
1021 } else {
1022 delete[] m_channel[i];
1023 error = InsufficientMemory;
1024 }
1025 }
1026 }
1027 }
1028 if (error != NoError) {
1029 // free already allocated memory
1030 for (int i=0; i < m_header.channels; i++) {
1031 delete m_wtChannel[i];
1032 }
1033 ReturnWithError(error);
1034 }
1035
1036 m_currentLevel = m_header.nLevels;
1037
1038 // create encoder, write headers and user data, but not level-length area
1039 m_encoder = new CEncoder(stream, m_preHeader, m_header, m_postHeader, m_userDataPos, m_useOMPinEncoder);
1040 if (m_favorSpeedOverSize) m_encoder->FavorSpeedOverSize();
1041
1042 #ifdef __PGFROISUPPORT__
1043 if (ROIisSupported()) {
1044 // new encoding scheme supporting ROI
1045 m_encoder->SetROI();
1046 }
1047 #endif
1048
1049 } else {
1050 // very small image: we don't use DWT and encoding
1051
1052 // create encoder, write headers and user data, but not level-length area
1053 m_encoder = new CEncoder(stream, m_preHeader, m_header, m_postHeader, m_userDataPos, m_useOMPinEncoder);
1054 }
1055
1056 INT64 nBytes = m_encoder->ComputeHeaderLength();
1057 return (nBytes > 0) ? (UINT32)nBytes : 0;
1058 }
UINT32 CPGFImage::WriteImage (CPGFStream * stream, CallbackPtr cb = nullptr, void * data = nullptr)
okay bestie, this is THE moment โ you're encoding and writing an image at the current stream position. ๐
Call this method after WriteHeader(). And if you wanna write uncached metadata, do that after WriteHeader() and before WriteImage(). This method gets called inside of Write(stream, ...). It might throw an IOException, no cap.
Parameters
streamโ a PGF stream, obviously. ๐cbโ a pointer to a callback procedure. The procedure is called after writing a single level. Ifcbreturnstrue, then it stops proceeding. (so dramatic, but okay)dataโ Data pointer to C++ class container to host callback procedure. ๐ซถ
Returns
The number of bytes written into stream. That's the tea. โ
Definition at line 1150 of file PGFimage.cpp.
1150 {
1151 ASSERT(stream);
1152 ASSERT(m_preHeader.hSize);
1153
1154 int levels = m_header.nLevels;
1155 double percent = pow(0.25, levels);
1156
1157 // update post-header size, rewrite pre-header, and write dummy levelLength
1158 UINT32 nWrittenBytes = UpdatePostHeaderSize();
1159
1160 if (levels == 0) {
1161 // for very small images: write channels uncoded
1162 for (int c=0; c < m_header.channels; c++) {
1163 const UINT32 size = m_width[c]*m_height[c];
1164
1165 // write channel data into stream
1166 for (UINT32 i=0; i < size; i++) {
1167 int count = DataTSize;
1168 stream->Write(&count, &m_channel[c][i]);
1169 }
1170 }
1171
1172 // now update progress
1173 if (cb) {
1174 if ((*cb)(1, true, data)) ReturnWithError(EscapePressed);
1175 }
1176
1177 } else {
1178 // encode quantized wavelet coefficients and write to PGF file
1179 // encode subbands, higher levels first
1180 // color channels are interleaved
1181
1182 // encode all levels
1183 for (m_currentLevel = levels; m_currentLevel > 0; ) {
1184 WriteLevel(); // decrements m_currentLevel
1185
1186 // now update progress
1187 if (cb) {
1188 percent *= 4;
1189 if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
1190 }
1191 }
1192
1193 // flush encoder and write level lengths
1194 m_encoder->Flush();
1195 }
1196
1197 // update level lengths
1198 nWrittenBytes += m_encoder->UpdateLevelLength(); // return written image bytes
1199
1200 // delete encoder
1201 delete m_encoder; m_encoder = nullptr;
1202
1203 ASSERT(!m_encoder);
1204
1205 return nWrittenBytes;
1206 }
void CPGFImage::WriteLevel () [private]
Definition at line 1068 of file PGFimage.cpp.
1068 {
1069 ASSERT(m_encoder);
1070 ASSERT(m_currentLevel > 0);
1071 ASSERT(m_header.nLevels > 0);
1072
1073 #ifdef __PGFROISUPPORT__
1074 if (ROIisSupported()) {
1075 const int lastChannel = m_header.channels - 1;
1076
1077 for (int i=0; i < m_header.channels; i++) {
1078 // get number of tiles and tile indices
1079 const UINT32 nTiles = m_wtChannel[i]->GetNofTiles(m_currentLevel);
1080 const UINT32 lastTile = nTiles - 1;
1081
1082 if (m_currentLevel == m_header.nLevels) {
1083 // last level also has LL band
1084 ASSERT(nTiles == 1);
1085 m_wtChannel[i]->GetSubband(m_currentLevel, LL)->ExtractTile(*m_encoder);
1086 m_encoder->EncodeTileBuffer(); // encode macro block with tile-end = true
1087 }
1088 for (UINT32 tileY=0; tileY < nTiles; tileY++) {
1089 for (UINT32 tileX=0; tileX < nTiles; tileX++) {
1090 // extract tile to macro block and encode already filled macro blocks with tile-end = false
1091 m_wtChannel[i]->GetSubband(m_currentLevel, HL)->ExtractTile(*m_encoder, true, tileX, tileY);
1092 m_wtChannel[i]->GetSubband(m_currentLevel, LH)->ExtractTile(*m_encoder, true, tileX, tileY);
1093 m_wtChannel[i]->GetSubband(m_currentLevel, HH)->ExtractTile(*m_encoder, true, tileX, tileY);
1094 if (i == lastChannel && tileY == lastTile && tileX == lastTile) {
1095 // all necessary data are buffered. next call of EncodeTileBuffer will write the last piece of data of the current level.
1096 m_encoder->SetEncodedLevel(--m_currentLevel);
1097 }
1098 m_encoder->EncodeTileBuffer(); // encode last macro block with tile-end = true
1099 }
1100 }
1101 }
1102 } else
1103 #endif
1104 {
1105 for (int i=0; i < m_header.channels; i++) {
1106 ASSERT(m_wtChannel[i]);
1107 if (m_currentLevel == m_header.nLevels) {
1108 // last level also has LL band
1109 m_wtChannel[i]->GetSubband(m_currentLevel, LL)->ExtractTile(*m_encoder);
1110 }
1111 //encoder.EncodeInterleaved(m_wtChannel[i], m_currentLevel, m_quant); // until version 4
1112 m_wtChannel[i]->GetSubband(m_currentLevel, HL)->ExtractTile(*m_encoder); // since version 5
1113 m_wtChannel[i]->GetSubband(m_currentLevel, LH)->ExtractTile(*m_encoder); // since version 5
1114 m_wtChannel[i]->GetSubband(m_currentLevel, HH)->ExtractTile(*m_encoder);
1115 }
1116
1117 // all necessary data are buffered. next call of EncodeBuffer will write the last piece of data of the current level.
1118 m_encoder->SetEncodedLevel(--m_currentLevel);
1119 }
1120 }
๐ Member Data Documentation ๐
RefreshCB CPGFImage::m_cb [private]
pointer to refresh callback procedure
Definition at line 545 of file PGFimage.h.
void* CPGFImage::m_cbArg [private]
refresh callback argument
Definition at line 546 of file PGFimage.h.
DataT* CPGFImage::m_channel[MaxChannels] [protected]
untransformed channels in YUV format
Definition at line 522 of file PGFimage.h.
int CPGFImage::m_currentLevel [protected]
transform level of current image
Definition at line 532 of file PGFimage.h.
CDecoder* CPGFImage::m_decoder [protected]
PGF decoder.
Definition at line 523 of file PGFimage.h.
bool CPGFImage::m_downsample [protected]
chrominance channels are downsampled
Definition at line 535 of file PGFimage.h.
CEncoder* CPGFImage::m_encoder [protected]
PGF encoder.
Definition at line 524 of file PGFimage.h.
bool CPGFImage::m_favorSpeedOverSize [protected]
favor encoding speed over compression ratio
Definition at line 536 of file PGFimage.h.
PGFHeader CPGFImage::m_header [protected]
PGF file header.
Definition at line 529 of file PGFimage.h.
UINT32 CPGFImage::m_height[MaxChannels] [protected]
height of each channel at current level
Definition at line 527 of file PGFimage.h.
UINT32* CPGFImage::m_levelLength [protected]
length of each level in bytes; first level starts immediately after this array
Definition at line 525 of file PGFimage.h.
double CPGFImage::m_percent [private]
progress [0..1]
Definition at line 547 of file PGFimage.h.
PGFPostHeader CPGFImage::m_postHeader [protected]
PGF post-header.
Definition at line 530 of file PGFimage.h.
PGFPreHeader CPGFImage::m_preHeader [protected]
PGF pre-header.
Definition at line 528 of file PGFimage.h.
ProgressMode CPGFImage::m_progressMode [private]
progress mode used in Read and Write; PM_Relative is default mode
Definition at line 548 of file PGFimage.h.
BYTE CPGFImage::m_quant [protected]
quantization parameter
Definition at line 534 of file PGFimage.h.
PGFRect CPGFImage::m_roi [protected]
region of interest
Definition at line 541 of file PGFimage.h.
bool CPGFImage::m_streamReinitialized [protected]
stream has been reinitialized
Definition at line 540 of file PGFimage.h.
bool CPGFImage::m_useOMPinDecoder [protected]
use Open MP in decoder
Definition at line 538 of file PGFimage.h.
bool CPGFImage::m_useOMPinEncoder [protected]
use Open MP in encoder
Definition at line 537 of file PGFimage.h.
UINT32 CPGFImage::m_userDataPolicy [protected]
user data (metadata) policy during open
Definition at line 533 of file PGFimage.h.
UINT64 CPGFImage::m_userDataPos [protected]
stream position of user data
Definition at line 531 of file PGFimage.h.
UINT32 CPGFImage::m_width[MaxChannels] [protected]
width of each channel at current level
Definition at line 526 of file PGFimage.h.
CWaveletTransform* CPGFImage::m_wtChannel[MaxChannels] [protected]
wavelet transformed color channels
Definition at line 521 of file PGFimage.h.
๐ธ Author ๐ธ
Generated automatically by Doxygen for libpgf from the source code. slay.
This iconic page is version 7.21.2, dated Thu Jul 24 2025 00:00:00, and it's CPGFImage(3). Bye bestie, go write some images! ๐โจ
๐ฆ where this page comes from
- fedora 43: libpgf-devel 7.21.7-8.fc43 ยท LicenseRef-Callaway-LGPLv2+
- fedora 43: libpgf-devel 7.21.7-8.fc43 ยท LicenseRef-Callaway-LGPLv2+
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms