//  Snooper.m
//  SnoopX
//
//  Created by tzhuan on 2008/8/4.
//  Copyright 2008 NTU CSIE CMLAB. All rights reserved.
//
//  HDR CAPTURE NOTES (Updated):
//  ============================
//  CRITICAL FIX: The cursor itself is captured as WHITE (1.0, 1.0, 1.0) even
//  though showsCursor=NO. This caused all HDR readings to be capped at 1.0!
//  
//  Solution: Offset pixel sampling by at least 10 logical points from the cursor
//  position to avoid reading the cursor shape. This allows true HDR values > 1.0
//  to be read from the buffer.
//  
//  Key Requirements for HDR:
//  1. Pixel Format: kCVPixelFormatType_64RGBAHalf (Float16 RGBA)
//  2. Color Space: kCGColorSpaceExtendedLinearDisplayP3
//  3. Cursor Offset: Sample 10 points away from cursor position
//  4. Coordinate Conversion: Use round() not truncation for accuracy
//

#import "Snooper.h"

/**
 * Options:
 *   CGWindowListCreateImage
 *   CGDisplayCreateImageForRect with CGBitmapContext
 *   CGBitmapContext with raw bitmap data
 */
// #define CAPTURE_USING_CG_WINDOW_LIST_CREATE_IMAGE
// #define CAPTURE_USING_CG_DISPLAY_CREATE_IMAGE_FOR_RECT_BITMAP_CONTEXT
#define CAPTURE_USING_CG_DISPLAY_CREATE_IMAGE_FOR_RECT_BITMAP_DATA

// Half-float (IEEE 754 binary16) to float conversion
static inline CGFloat halfFloatToFloat(uint16_t h) {
	uint32_t sign = (h & 0x8000) << 16;
	uint32_t exponent = (h & 0x7C00) >> 10;
	uint32_t mantissa = (h & 0x03FF);
	
	if (exponent == 0) {
		if (mantissa == 0) return 0.0f; // Zero
		// Denormalized number
		return (sign ? -1.0f : 1.0f) * powf(2.0f, -14.0f) * (mantissa / 1024.0f);
	} else if (exponent == 31) {
		// Infinity or NaN
		return (mantissa == 0) ? (sign ? -INFINITY : INFINITY) : NAN;
	}
	
	// Normalized number
	uint32_t floatBits = sign | (((exponent - 15 + 127) & 0xFF) << 23) | (mantissa << 13);
	return *((float *)&floatBits);
}

// Float to half-float conversion
static inline uint16_t floatToHalfFloat(float f) {
	uint32_t floatBits = *((uint32_t *)&f);
	uint32_t sign = (floatBits >> 16) & 0x8000;
	int32_t exponent = ((floatBits >> 23) & 0xFF) - 127 + 15;
	uint32_t mantissa = floatBits & 0x7FFFFF;
	
	// Handle special cases
	if (exponent <= 0) {
		// Too small - flush to zero
		return (uint16_t)sign;
	} else if (exponent >= 31) {
		// Too large - infinity
		return (uint16_t)(sign | 0x7C00);
	}
	
	// Normal case
	return (uint16_t)(sign | (exponent << 10) | (mantissa >> 13));
}

// Release callback for CGDataProvider
static void releaseDataCallback(void *info, const void *data, size_t size) {
	free((void *)data);
}

// Compare function for qsort (used in HDR statistics median calculation)
static int compare_cgfloat(const void *a, const void *b) {
	CGFloat fa = *(const CGFloat *)a;
	CGFloat fb = *(const CGFloat *)b;
	return (fa > fb) - (fa < fb);
}

void clearBitmapData(unsigned char* data, size_t width, size_t height, size_t bytesPerPixel, size_t bytesPerRow)
{
	unsigned char* row = data;
	for (size_t h = 0; h < height; ++h) {
		unsigned char* pixel = row;
		for (size_t w = 0; w < width; ++w) {
			pixel[ALPHA_INDEX] = 255;
			pixel += bytesPerPixel;
		}
		row += bytesPerRow;
	}
}

void drawBitmapData(
	const unsigned char* src, size_t srcWidth, size_t srcHeight, size_t srcBytesPerRow,
	unsigned char* dst, size_t dstWidth, size_t dstHeight, size_t dstBytesPerRow,
	size_t bytesPerPixel,
	size_t rectX, size_t rectY, size_t rectWidth, size_t rectHeight
) {
#ifdef SNOOPX_VERBOSE
	// drawBitmapData debug logging removed
#endif
	const unsigned char* srcRow = src;
	unsigned char* dstRow = dst;

	// draw pixels at once
	if (rectX == 0 && rectY == 0 &&
		rectWidth == srcWidth && rectHeight == srcHeight &&
		rectWidth == dstWidth && rectHeight == dstHeight &&
		srcBytesPerRow == dstBytesPerRow) {
		memcpy(dst, src, dstHeight*dstBytesPerRow);
		return;
	}

	// draw pixels row by row
	dstRow += rectY*dstBytesPerRow + rectX*bytesPerPixel;
	if (rectWidth == srcWidth && rectHeight == srcHeight) {
		for (size_t h = 0; h < rectHeight; ++h) {
			memcpy(dstRow, srcRow, rectWidth*bytesPerPixel);
			srcRow += srcBytesPerRow;
			dstRow += dstBytesPerRow;
		}
		return;
	} 

	// draw pixel by pixel
	double scaleWidth = (double)srcWidth/rectWidth;
	double scaleHeight = (double)srcHeight/rectHeight;
	for (size_t h = 0; h < rectHeight; ++h) {
		size_t srcH = floor(h*scaleHeight);
		const unsigned char* srcPixel = srcRow + srcH*srcBytesPerRow;
		unsigned char* dstPixel = dstRow;
		for (size_t w = 0; w < rectWidth; ++w) {
			size_t srcW = floor(w*scaleWidth);
			memcpy(dstPixel, srcPixel + srcW*bytesPerPixel, bytesPerPixel);
			dstPixel += bytesPerPixel;
		}
		dstRow += dstBytesPerRow;
	}
}

@implementation Snooper {
	// Store the current display for ScreenCaptureKit
	SCDisplay *currentDisplay API_AVAILABLE(macos(12.3));
	NSRect currentDisplayFrame; // Screen coordinates of the current display
	CGDirectDisplayID targetDisplayID; // The display ID we want to capture
	BOOL useHDRScaleForGraphs; // Whether graphs are using 4.0 scale (vs 1.0 scale)
}

@synthesize cursorRed;
@synthesize cursorGreen;
@synthesize cursorBlue;
@synthesize cursorAlpha;
@synthesize cursorRedHDR;
@synthesize cursorGreenHDR;
@synthesize cursorBlueHDR;
@synthesize cursorAlphaHDR;
@synthesize highlight;
@synthesize graph;
@synthesize statistics;
@synthesize swatch;

- (id) init 
{
	if ((self = [super init])) {
		capturePointWidth = 0;
		capturePointHeight = 0;
		zoomPointWidth = 0;
		zoomPointHeight = 0;
		viewPointWidth = 0;
		viewPointHeight = 0;

		capturePixelCenterX = 0;
		capturePixelCenterY = 0;

		zoomPixelCenterX = 0;
		zoomPixelCenterY = 0;

		gridPixelSize = 0;

		actualCapturedWidth = 0;
		actualCapturedHeight = 0;

		actualSamplePointX = 0.0;
		actualSamplePointY = 0.0;

		useHDRScaleForGraphs = NO;
		displayMode = DisplayModeAuto;

		capturePixelWidth = 0;
		capturePixelHeight = 0;
		zoomPixelWidth = 0;
		zoomPixelHeight = 0;
		viewPixelWidth = 0;
		viewPixelHeight = 0;
		cropPixelX = 0;
		cropPixelY = 0;

		captureContext = nil;
		zoomContext = nil;
		viewContext = nil;

		cursorPointX = 0;
		cursorPointY = 0;
		
		cursorRedHDR = 0.0;
		cursorGreenHDR = 0.0;
		cursorBlueHDR = 0.0;
		cursorAlphaHDR = 0.0;

		maxBackingScaleFactor = 0.0;
		capturePointRect = NSZeroRect;
		
		currentDisplayFrame = NSZeroRect;

		// Initialize with standard RGB, will upgrade to HDR if available
		isHDRCapable = NO;
		useHDRCapture = NO;
		useScreenCaptureKit = NO;
		latestPixelBuffer = NULL;
		pixelBufferSemaphore = dispatch_semaphore_create(1);
		captureStream = nil;
		contentFilter = nil;
		
		if (@available(macOS 12.3, *)) {
			currentDisplay = nil;
			targetDisplayID = 0; // Will be set to cursor's display
		}
		
		colorSpace = CGColorSpaceCreateDeviceRGB();
		bitsPerComponent = 8;
		bitsPerPixel = bitsPerComponent*4;
		bytesPerPixel = bitsPerPixel/8;
		alphaInfo = kCGImageAlphaPremultipliedFirst;
		bitmapInfo = kCGBitmapByteOrder32Little | alphaInfo;

		captureData = NULL;
		captureDataWidth = 0;
		captureDataHeight = 0;
		captureDataBytesPerRow = 0;

		cgRed = CGColorCreateGenericRGB(1.0, 0.0, 0.0, 1.0);
		cgGreen = CGColorCreateGenericRGB(0.0, 1.0, 0.0, 1.0);
		cgBlue = CGColorCreateGenericRGB(0.0, 0.0, 1.0, 1.0);
		cgWhite = CGColorGetConstantColor(kCGColorWhite);
		cgBlack = CGColorGetConstantColor(kCGColorBlack);
		cgGray = CGColorCreateGenericRGB(0.15, 0.15, 0.15, 1.0);

		statisticsInfoString[0] = '\0';
	}
	return self;
}

- (void) dealloc
{
	// Stop ScreenCaptureKit if running
	if (@available(macOS 12.3, *)) {
		if (captureStream) {
			[self stopScreenCaptureKit];
		}
	}
	
	if (latestPixelBuffer) {
		CVPixelBufferRelease(latestPixelBuffer);
		latestPixelBuffer = NULL;
	}
	
	if (colorSpace) {
		CGColorSpaceRelease(colorSpace);
		colorSpace = NULL;
	}
	if (captureData) {
		free(captureData);
		captureData = NULL;
	}

	CGColorRelease(cgRed);
	CGColorRelease(cgGreen);
	CGColorRelease(cgBlue);
	CGColorRelease(cgGray);
	[super dealloc];
}

- (void) setCursorPointX:(CGFloat)x Y:(CGFloat)y
{
	if (cursorPointX != x || cursorPointY != y) {
		cursorPointX = x;
		cursorPointY = y;
		[self resetCapturePointRect];
	}
}

- (CGFloat) getActualSamplePointX
{
	return actualSamplePointX;
}

- (CGFloat) getActualSamplePointY
{
	return actualSamplePointY;
}

- (void) setViewPointWidth:(size_t)width Height:(size_t)height
{
	if (viewPointWidth != width || viewPointHeight != height) {
		viewPointWidth = width;
		viewPointHeight = height;
		[self resetPointSizes];
		[self resetCapturePointRect];
	}
}

- (void) setZoom:(size_t)z
{
	if (zoom != z) {
		zoom = z;
		[self resetPointSizes];
		[self resetCapturePointRect];
	}
}

- (void) resetPointSizes
{
	capturePointWidth = ceil((double)viewPointWidth/zoom);
	capturePointHeight = ceil((double)viewPointHeight/zoom);
	zoomPointWidth = capturePointWidth*zoom;
	zoomPointHeight = capturePointHeight*zoom;
	[self resetPixelSizes];
#ifdef SNOOPX_VERBOSE
#endif
}

- (void) resetPixelSizes
{
	viewPixelWidth = ceil(viewPointWidth*maxBackingScaleFactor);
	viewPixelHeight = ceil(viewPointHeight*maxBackingScaleFactor);
	capturePixelWidth = ceil(capturePointWidth*maxBackingScaleFactor);
	capturePixelHeight = ceil(capturePointHeight*maxBackingScaleFactor);
	zoomPixelWidth = ceil(zoomPointWidth*maxBackingScaleFactor);
	zoomPixelHeight = ceil(zoomPointHeight*maxBackingScaleFactor);
	cropPixelX  = zoomPixelWidth - viewPixelWidth;
	cropPixelY = zoomPixelHeight - viewPixelHeight;

	// Ensure grid size is an integer to prevent straddling
	gridPixelSize = (size_t)floor(zoom*maxBackingScaleFactor / 2.0);

	// origin is upper-left
	// CRITICAL: Must use round(), not floor(), to match how cursor and rect are rounded
	// CRITICAL: Use floating-point division, not integer, so odd capturePointWidth/Height
	// rounds to the correct center (e.g. 43/2.0=21.5 → round(43.0)=43, not 42).
	capturePixelCenterX = (size_t)round(capturePointWidth * maxBackingScaleFactor / 2.0);
	capturePixelCenterY = (size_t)round(capturePointHeight * maxBackingScaleFactor / 2.0);


	// origin is bottom-left
	// Use floor() to ensure pixel-perfect alignment on both axes
	// Calculate the zoomed center position by flooring each step
	zoomPixelCenterX = (size_t)floor(capturePixelCenterX * zoom);
	
	// For Y: calculate from bottom, accounting for grid size
	// The red square should start at: (totalHeight - centerY*zoom - gridSize)
	// We need to floor this to prevent straddling
	CGFloat rawCenterY = zoomPixelHeight - (capturePixelCenterY * zoom) - gridPixelSize;
	zoomPixelCenterY = (size_t)floor(rawCenterY);

#ifdef SNOOPX_VERBOSE

#endif
}

- (void) resetCapturePointRect
{
	// TEST: Try no offset since showsCursor=NO should exclude cursor
	// If cursor appears as white in capture, we'll need to restore the offset
	CGFloat cursorOffsetPoints = 0.0;  // Changed from 10.0 to test
	CGFloat offsetX = -cursorOffsetPoints; // No offset
	CGFloat offsetY = cursorOffsetPoints;   // No offset

	// The actual point we're sampling is offset from cursor
	CGFloat samplePointX = cursorPointX + offsetX;
	CGFloat samplePointY = cursorPointY + offsetY;

	// Center the capture rectangle on the SAMPLE point, not the cursor
	// CRITICAL: Round to integer POINT boundaries to avoid sub-pixel interpolation
	CGFloat halfWidth = capturePointWidth / 2.0;
	CGFloat halfHeight = capturePointHeight / 2.0;

	// Clamp sample point to ensure capture rectangle stays within display bounds
	// This prevents coordinates from changing when too close to screen edges
	CGFloat displayLeft = currentDisplayFrame.origin.x;
	CGFloat displayRight = currentDisplayFrame.origin.x + currentDisplayFrame.size.width;
	CGFloat displayBottom = currentDisplayFrame.origin.y;
	CGFloat displayTop = currentDisplayFrame.origin.y + currentDisplayFrame.size.height;

	// Clamp X: ensure capture rect fits within horizontal bounds
	CGFloat minSampleX = displayLeft + halfWidth;
	CGFloat maxSampleX = displayRight - halfWidth;
	if (samplePointX < minSampleX) samplePointX = minSampleX;
	if (samplePointX > maxSampleX) samplePointX = maxSampleX;

	// Clamp Y: ensure capture rect fits within vertical bounds
	CGFloat minSampleY = displayBottom + halfHeight;
	CGFloat maxSampleY = displayTop - halfHeight;
	if (samplePointY < minSampleY) samplePointY = minSampleY;
	if (samplePointY > maxSampleY) samplePointY = maxSampleY;

	// Store the actual (clamped) sample position for display
	actualSamplePointX = samplePointX;
	actualSamplePointY = samplePointY;

	// Align origin to physical pixel boundaries (0.5-point steps on 2x Retina)
	// Rounding to 1-physical-pixel ensures every 1-pixel nudge shifts the capture rect
	capturePointRect.origin.x = round((samplePointX - halfWidth) * maxBackingScaleFactor) / maxBackingScaleFactor;
	capturePointRect.origin.y = round((samplePointY - halfHeight) * maxBackingScaleFactor) / maxBackingScaleFactor;
	capturePointRect.size.width = capturePointWidth;
	capturePointRect.size.height = capturePointHeight;
}

- (unsigned char*) initCaptureData
{
	if (capturePixelWidth == 0 || capturePixelHeight == 0)
		return NULL;

	size_t bytes = capturePixelWidth*capturePixelHeight*bytesPerPixel;
	if (captureDataWidth != capturePixelWidth ||
		captureDataHeight != capturePixelHeight ||
		captureData == NULL) {
		if (captureData)
			free(captureData);
		captureData = malloc(bytes);
		if (captureData == NULL) {
			captureDataWidth = 0;
			captureDataHeight = 0;
			captureDataBytesPerRow = 0;
			return NULL;
		}
		captureDataWidth = capturePixelWidth;
		captureDataHeight = capturePixelHeight;
		captureDataBytesPerRow = capturePixelWidth*bytesPerPixel;
	}
	bzero(captureData, bytes);
	clearBitmapData(captureData, captureDataWidth, captureDataHeight, bytesPerPixel, captureDataBytesPerRow);
	return captureData;
}

- (CGContextRef) initCaptureContextWithImage:(CGImageRef)screenshot
{
	if (capturePixelWidth == 0 || capturePixelHeight == 0)
		return nil;
	if (captureContext == nil ||
		CGBitmapContextGetWidth(captureContext) != capturePixelWidth ||
		CGBitmapContextGetHeight(captureContext) != capturePixelHeight
	) {
#ifdef SNOOPX_VERBOSE
#endif
		if (captureContext != nil)
			CGContextRelease(captureContext);

		// use capture image's color space
		if (!colorSpace)
			colorSpace = CGImageGetColorSpace(screenshot);

#ifndef DEBUG
#endif
		captureContext = CGBitmapContextCreate(
			NULL,
			capturePixelWidth, capturePixelHeight,
			CGImageGetBitsPerComponent(screenshot),
			// bitsPerComponent,
			0,
			colorSpace,
			bitmapInfo
		);
	}
	// set background to black
	CGContextSetBlendMode(zoomContext, kCGBlendModeNormal);
	CGContextSetFillColorWithColor(captureContext, cgBlack);
	CGRect rect = {CGPointZero, {capturePixelWidth, capturePixelHeight}};
	CGContextFillRect(captureContext, rect);
	return captureContext;
}

- (CGContextRef) initZoomContext
{
	if (zoomPixelWidth == 0 || zoomPixelHeight == 0)
		return nil;
	if (zoomContext == nil ||
		CGBitmapContextGetWidth(zoomContext) != zoomPixelWidth ||
		CGBitmapContextGetHeight(zoomContext) != zoomPixelHeight
	) {
		if (zoomContext != nil)
			CGContextRelease(zoomContext);

		zoomContext = CGBitmapContextCreate(
			NULL,
			zoomPixelWidth, zoomPixelHeight,
			bitsPerComponent,
			0,
			colorSpace,
			bitmapInfo
		);
		CGContextSetInterpolationQuality(zoomContext, kCGInterpolationNone);
	}
	return zoomContext;
}

- (void) captureCursorColor
{
	// IMPORTANT: We always read from the CENTER of captureData
	// This is the pixel that the red box highlights in the zoomed view
	// Both ScreenCaptureKit and legacy capture write to captureData,
	// so we read from the same place regardless of capture method
	
#ifdef CAPTURE_USING_CG_DISPLAY_CREATE_IMAGE_FOR_RECT_BITMAP_DATA
	unsigned char *data = captureData +
		capturePixelCenterY*captureDataBytesPerRow +
		capturePixelCenterX*bytesPerPixel;
	
	// Read as 8-bit
	cursorBlue = data[BLUE_INDEX];
	cursorGreen = data[GREEN_INDEX];
	cursorRed = data[RED_INDEX];
	cursorAlpha = data[ALPHA_INDEX];

	
	// For HDR displays, try to get true HDR values from the buffer
	// But we'll still use the 8-bit values if ScreenCaptureKit isn't available
	BOOL gotHDRValues = NO;

	if (@available(macOS 12.3, *)) {
		if (useScreenCaptureKit && latestPixelBuffer && [self isHDRActive]) {
			dispatch_semaphore_wait(pixelBufferSemaphore, DISPATCH_TIME_FOREVER);
			CVPixelBufferRef buffer = latestPixelBuffer;
			if (buffer) {
				CVPixelBufferRetain(buffer);
			}
			dispatch_semaphore_signal(pixelBufferSemaphore);
			
			if (buffer) {
				// Calculate the pixel position in the full-screen buffer
				// ScreenCaptureKit buffer uses TOP-LEFT origin (CoreGraphics)
				// AppKit cursor uses BOTTOM-LEFT origin in GLOBAL space
				// We need to translate to display-local coordinates first
				
				size_t bufferWidth = CVPixelBufferGetWidth(buffer);
				size_t bufferHeight = CVPixelBufferGetHeight(buffer);
				
				// CRITICAL: Calculate the actual scale factor from buffer size
				// The buffer might NOT be at maxBackingScaleFactor resolution!
				CGFloat bufferScaleX = bufferWidth / currentDisplayFrame.size.width;
				CGFloat bufferScaleY = bufferHeight / currentDisplayFrame.size.height;

				// CRITICAL: Use the EXACT same calculation as snoopCGImageFrom to find which
				// pixel we're sampling. Calculate the capture rect, then find its center.
				// This ensures perfect alignment between displayed and sampled pixels.

				// Convert capture rect to display-relative coordinates (same as snoopCGImageFrom)
				CGFloat localRectX = capturePointRect.origin.x - currentDisplayFrame.origin.x;
				CGFloat localRectY = capturePointRect.origin.y - currentDisplayFrame.origin.y;
				CGFloat localRectYCG = currentDisplayFrame.size.height - localRectY - capturePointRect.size.height;

				// Convert to buffer pixel coordinates (same formula as snoopCGImageFrom)
				CGFloat rawCaptureX = round(localRectX * bufferScaleX);
				CGFloat rawCaptureY = round(localRectYCG * bufferScaleY);
				if (rawCaptureX < 0) rawCaptureX = 0;
				if (rawCaptureY < 0) rawCaptureY = 0;
				size_t captureX = (size_t)rawCaptureX;
				size_t captureY = (size_t)rawCaptureY;
				size_t captureWidth = (size_t)round(capturePointRect.size.width * bufferScaleX);
				size_t captureHeight = (size_t)round(capturePointRect.size.height * bufferScaleY);

				// Find the center pixel of the capture region
				size_t pixelX = captureX + captureWidth / 2;
				size_t pixelY = captureY + captureHeight / 2;

				// Bounds check
				if (pixelX >= bufferWidth) pixelX = bufferWidth - 1;
				if (pixelY >= bufferHeight) pixelY = bufferHeight - 1;

				// Read HDR pixel directly from buffer
				if ([self readHDRPixelFromBuffer:buffer atX:pixelX Y:pixelY]) {
					gotHDRValues = YES;
				}
				
				CVPixelBufferRelease(buffer);
			}
		}
	}
	
	// If we didn't get HDR values, convert the 8-bit values to normalized range
	if (!gotHDRValues) {
		cursorBlueHDR = cursorBlue / 255.0;
		cursorGreenHDR = cursorGreen / 255.0;
		cursorRedHDR = cursorRed / 255.0;
		cursorAlphaHDR = cursorAlpha / 255.0;
	}
#endif

#ifdef CAPTURE_USING_CG_DISPLAY_CREATE_IMAGE_FOR_RECT_BITMAP_CONTEXT
	size_t bitsPerPixel = CGBitmapContextGetBitsPerPixel(captureContext);
	size_t bytesPerPixel = bitsPerPixel/8;
	const unsigned char *data = CGBitmapContextGetData(captureContext);
	size_t bytesPerRow = CGBitmapContextGetBytesPerRow(captureContext);
	data += (capturePixelHeight/2)*bytesPerRow + (capturePixelWidth/2)*bytesPerPixel;
	
	cursorBlue = data[BLUE_INDEX];
	cursorGreen = data[GREEN_INDEX];
	cursorRed = data[RED_INDEX];
	cursorAlpha = data[ALPHA_INDEX];
	
	cursorBlueHDR = cursorBlue / 255.0;
	cursorGreenHDR = cursorGreen / 255.0;
	cursorRedHDR = cursorRed / 255.0;
	cursorAlphaHDR = cursorAlpha / 255.0;
#endif
}

- (CGContextRef) getZoomContext
{
	if ([self initZoomContext] == nil) {
		NSLog(@"[ERROR] zoomContext is nil");
		return nil;
	}
	CGImageRef captureImage = CGBitmapContextCreateImage(captureContext);
	if (captureImage == nil) {
		NSLog(@"[ERROR] captureImage is nil");
		return nil;
	}

	// Direct nearest-neighbor pixel copy: captureData row r → zoom buffer rows r*zoom to (r+1)*zoom-1.
	// This matches the HDR path's coordinate convention and avoids the 1-row shift
	// caused by CGContextDrawImage's upside-down drawing in a Quartz Y-up context.
	CGImageRelease(captureImage);
	unsigned char *zoomData = CGBitmapContextGetData(zoomContext);
	size_t zoomBPR = CGBitmapContextGetBytesPerRow(zoomContext);
	for (size_t r = 0; r < zoomPixelHeight; r++) {
		size_t srcRow = r / zoom;
		const unsigned char *srcRowPtr = captureData + srcRow * captureDataBytesPerRow;
		unsigned char *dstRowPtr = zoomData + r * zoomBPR;
		for (size_t x = 0; x < zoomPixelWidth; x++) {
			size_t srcCol = x / zoom;
			memcpy(dstRowPtr + x * bytesPerPixel,
			       srcRowPtr + srcCol * bytesPerPixel,
			       bytesPerPixel);
		}
	}
	return zoomContext;
}

- (CGImageRef) getCropImage
{
	CGImageRef zoomImage = CGBitmapContextCreateImage(zoomContext);
	if (zoomImage == nil) {
		NSLog(@"[ERROR] zoomImage is nil");
		return nil;
	}
	if (cropPixelX > 0 || cropPixelY > 0) {
		// Use half of the excess crop symmetrically on each side so the cursor stays centered,
		// matching the HDR path which scales the full zoom buffer to the view.
		size_t cropOffsetX = cropPixelX / 2;
		size_t cropOffsetY = cropPixelY / 2;

		CGRect rect = {{cropOffsetX, cropOffsetY}, {viewPixelWidth, viewPixelHeight}};
		CGImageRef cropImage = CGImageCreateWithImageInRect(zoomImage, rect);
		CGImageRelease(zoomImage);
		return cropImage;
	} 
	return zoomImage;
}

- (CGImageRef) getScreenshotFor:(id)screen In:(CGRect)rect
{
	NSDictionary *description = [screen deviceDescription];
	CGDirectDisplayID displayID = (CGDirectDisplayID)[[description valueForKey:@"NSScreenNumber"] unsignedIntValue];
	
	// For HDR, try alternative capture method
	CGImageRef image = nil;
	
	if (@available(macOS 10.15, *)) if (useHDRCapture && isHDRCapable) {
		// Try window list capture which may preserve more dynamic range
		// Convert rect to screen coordinates
		NSScreen *mainScreen = [NSScreen mainScreen];
		CGFloat mainHeight = [mainScreen frame].size.height;
		CGRect screenRect = rect;
		screenRect.origin.y = mainHeight - rect.origin.y - rect.size.height;
		
		// Use CGWindowListCreateImage with HDR-preserving options
		image = CGWindowListCreateImage(
			screenRect,
			kCGWindowListOptionOnScreenOnly,
			kCGNullWindowID,
			kCGWindowImageBoundsIgnoreFraming | kCGWindowImageNominalResolution
		);
		
#ifdef SNOOPX_VERBOSE
		if (image) {
		}
#endif
	}
	
	// Fallback to standard capture
	if (!image) {
		image = CGDisplayCreateImageForRect(displayID, rect);
	}
	
#ifdef SNOOPX_VERBOSE
#endif
	
	return image;
}

- (CGContextRef) getCaptureContextFrom:(NSArray*)screens
{
	// Try ScreenCaptureKit first for HDR (macOS 12.3+)
	if (@available(macOS 12.3, *)) {
		if (useScreenCaptureKit && latestPixelBuffer) {
			dispatch_semaphore_wait(pixelBufferSemaphore, DISPATCH_TIME_FOREVER);
			CVPixelBufferRef buffer = latestPixelBuffer;
			if (buffer) {
				CVPixelBufferRetain(buffer);
			}
			dispatch_semaphore_signal(pixelBufferSemaphore);
			
			if (buffer) {
				// Initialize capture data buffer
				if ([self initCaptureData] == NULL) {
					NSLog(@"❌ [ERROR]: captureData is NULL");
					CVPixelBufferRelease(buffer);
					return nil;
				}
				
				// Calculate region to extract from full-screen buffer
				// COORDINATE SYSTEMS:
				// - AppKit (NSScreen): Y=0 at BOTTOM-LEFT, Y increases UPWARD
				// - CoreGraphics (buffer): Y=0 at TOP-LEFT, Y increases DOWNWARD
				// - capturePointRect: in GLOBAL AppKit coordinates (POINTS)
				// - buffer: in CoreGraphics coordinates (PIXELS)

				size_t bufferWidth = CVPixelBufferGetWidth(buffer);
				size_t bufferHeight = CVPixelBufferGetHeight(buffer);

				// CRITICAL FIX: Detect the actual scale factor from buffer vs display frame
				// The buffer might be at native resolution while NSScreen reports scaled size
				CGFloat actualScaleX = bufferWidth / currentDisplayFrame.size.width;

				// Use the actual scale from the buffer
				CGFloat scaleToUse = actualScaleX; // Assume X and Y are same

				// CRITICAL: Recalculate capture size using the ACTUAL buffer scale
				// The capturePixelWidth/Height were calculated with maxBackingScaleFactor,
				// but we need them in the buffer's actual scale
				size_t actualCaptureWidth = (size_t)floor(capturePointRect.size.width * scaleToUse);
				size_t actualCaptureHeight = (size_t)floor(capturePointRect.size.height * scaleToUse);

				// Convert capturePointRect from GLOBAL AppKit to DISPLAY-LOCAL AppKit
				// CRITICAL: Do all floating-point math first, then round once at pixel conversion
				CGFloat localRectX = capturePointRect.origin.x - currentDisplayFrame.origin.x;
				CGFloat localRectY = capturePointRect.origin.y - currentDisplayFrame.origin.y;

				// Flip Y from AppKit (bottom-origin) to CoreGraphics (top-origin)
				CGFloat localRectYCG = currentDisplayFrame.size.height - localRectY - capturePointRect.size.height;

				// Round to nearest pixel, clamping negatives to 0
				CGFloat rawRectX = round(localRectX * scaleToUse);
				CGFloat rawRectY = round(localRectYCG * scaleToUse);
				if (rawRectX < 0) rawRectX = 0;
				if (rawRectY < 0) rawRectY = 0;
				size_t rectX = (size_t)rawRectX;
				size_t rectY = (size_t)rawRectY;

				// Clamp to buffer bounds (handles cursor at screen edges)
				if (rectX >= bufferWidth) rectX = bufferWidth > actualCaptureWidth ? bufferWidth - actualCaptureWidth : 0;
				if (rectY >= bufferHeight) rectY = bufferHeight > actualCaptureHeight ? bufferHeight - actualCaptureHeight : 0;
				if (rectX + actualCaptureWidth > bufferWidth) actualCaptureWidth = bufferWidth - rectX;
				if (rectY + actualCaptureHeight > bufferHeight) actualCaptureHeight = bufferHeight - rectY;
				NSLog(@"[DIAG SDR-CTX] rectX=%zu rectY=%zu actualCaptureW=%zu actualCaptureH=%zu capturePixelH=%zu capturePixelCenterY=%zu",
					rectX, rectY, actualCaptureWidth, actualCaptureHeight, capturePixelHeight, capturePixelCenterY);


				// Extract and convert HDR region to 8-bit RGB
				// The buffer scale might not match maxBackingScaleFactor,
				// so we extract at actualCaptureWidth/Height but need to scale
				// to capturePixelWidth/Height for the rest of the pipeline
				
				BOOL needsScaling = (actualCaptureWidth != capturePixelWidth || 
				                     actualCaptureHeight != capturePixelHeight);
				
				unsigned char *tempData = captureData;
				size_t tempBytesPerRow = captureDataBytesPerRow;
				
				if (needsScaling) {
					// Allocate temporary buffer for extraction at actual size
					tempBytesPerRow = actualCaptureWidth * bytesPerPixel;
					tempData = malloc(actualCaptureHeight * tempBytesPerRow);
					if (!tempData) {
						NSLog(@"❌ Failed to allocate temporary buffer");
						CVPixelBufferRelease(buffer);
						return nil;
					}
#ifdef SNOOPX_VERBOSE
					NSLog(@"⚠️ Scaling needed: extracting %zux%zu, scaling to %zux%zu",
						actualCaptureWidth, actualCaptureHeight,
						capturePixelWidth, capturePixelHeight);
#endif
				}
				
				BOOL success = [self extractRegionFromHDRBuffer:buffer
														 toData:tempData
														  width:actualCaptureWidth
														 height:actualCaptureHeight
												   bytesPerRow:tempBytesPerRow
														 rectX:rectX
														 rectY:rectY];
				
				if (success && needsScaling) {
					// Scale from tempData to captureData
					drawBitmapData(
						tempData, actualCaptureWidth, actualCaptureHeight, tempBytesPerRow,
						captureData, capturePixelWidth, capturePixelHeight, captureDataBytesPerRow,
						bytesPerPixel,
						0, 0, capturePixelWidth, capturePixelHeight
					);
					free(tempData);
				}
				
				CVPixelBufferRelease(buffer);
				
				if (success) {
					// Create bitmap context from our converted data
					if (captureContext)
						CGContextRelease(captureContext);
					captureContext = CGBitmapContextCreate(
						captureData, captureDataWidth, captureDataHeight,
						bitsPerComponent, captureDataBytesPerRow,
						colorSpace, bitmapInfo
					);
					
					if (captureContext) {
#ifdef SNOOPX_VERBOSE
						NSLog(@"✅ ScreenCaptureKit capture successful");
#endif
						return captureContext;
					} else {
						NSLog(@"❌ Failed to create bitmap context from ScreenCaptureKit data");
					}
				} else {
					NSLog(@"❌ Failed to extract region from HDR buffer");
				}
			}
		}
	}
	
	// Fallback to legacy capture method
#ifdef SNOOPX_VERBOSE
	if (isHDRCapable) {
		NSLog(@"⚠️ Falling back to legacy capture (ScreenCaptureKit not ready)");
	}
#endif
	
	/**
	 * Use CGDisplayCreateImage for 10.6+
	 * https://developer.apple.com/library/mac/#qa/qa1741/_index.html
	 */
	
#ifdef CAPTURE_USING_CG_WINDOW_LIST_CREATE_IMAGE
	CGRect rect = {
		{capturePointRect.origin.x, mainScreenHeight - capturePointRect.origin.y - capturePointRect.size.height},
		{capturePointRect.size.width, capturePointRect.size.height}
	};
	CGImageRef screenshot = CGWindowListCreateImage(
		rect, kCGWindowListOptionAll,
		kCGNullWindowID, kCGWindowImageBestResolution
	);
	CGRect location = {
		{0, 0},
		{capturePointRect.size.width*maxBackingScaleFactor, capturePointRect.size.height*maxBackingScaleFactor}
	};	
	[self drawImage:screenshot At:location];
	if (captureContext)
		CGContextRelease(captureContext);
	captureContext = CGBitmapContextCreate(
		captureData, captureDataWidth, captureDataHeight,
		bitsPerComponent, captureDataBytesPerRow,
		colorSpace, bitmapInfo
	);
	return captureContext;
#endif

#ifdef CAPTURE_USING_CG_DISPLAY_CREATE_IMAGE_FOR_RECT_BITMAP_DATA
	if ([self initCaptureData] == NULL) {
		NSLog(@"[ERROR]: captureData is NULL");
		return nil;
	}
#endif

#ifdef CAPTURE_USING_CG_DISPLAY_CREATE_IMAGE_FOR_RECT_BITMAP_CONTEXT
	BOOL first = YES;
#endif
	for (id screen in screens) {
		NSRect screenRect = [screen frame];
#ifdef SNOOPX_VERBOSE
#endif
		NSRect is = NSIntersectionRect(screenRect, capturePointRect);
		// if capturePointRect overlaps this screen, capture it
		if (is.size.width > 0 && is.size.height > 0) {
#ifdef SNOOPX_VERBOSE
#endif
			// display space: origin is at upper-left
			CGRect rect = {
				{
					is.origin.x - screenRect.origin.x, 
					(screenRect.size.height - is.size.height) - 
						(is.origin.y - screenRect.origin.y)
				},
				{is.size.width, is.size.height}
			};
			CGImageRef screenshot = [self getScreenshotFor:screen In:rect];
			if (screenshot == nil) {
				NSLog(@"[ERROR] screenshot is nil.");
				continue;
			}

#ifdef CAPTURE_USING_CG_DISPLAY_CREATE_IMAGE_FOR_RECT_BITMAP_CONTEXT
			if (first) {
				if ([self initCapturedContextWithImage:screenshot] == nil) {
					NSLog(@"[ERROR]: capturedContext is nil");
					continue;
				}
				first = NO;
			}
#endif

			// place screenshot at the correct location
			size_t width = is.size.width*maxBackingScaleFactor;
			size_t height = is.size.height*maxBackingScaleFactor;
			size_t x = (is.origin.x-capturePointRect.origin.x)*maxBackingScaleFactor;

#ifdef CAPTURE_USING_CG_DISPLAY_CREATE_IMAGE_FOR_RECT_BITMAP_DATA
			// origin is at upper-left corner 
			size_t y = capturePixelHeight - (is.origin.y-capturePointRect.origin.y)*maxBackingScaleFactor - height;
#endif
			
#ifdef CAPTURE_USING_CG_DISPLAY_CREATE_IMAGE_FOR_RECT_BITMAP_CONTEXT
			// origin is at bottom-left corner 
			size_t y = (is.origin.y-capturePointRect.origin.y)*maxBackingScaleFactor;
#endif
			
			CGRect location = {{x, y}, {width, height}};

#ifdef CAPTURE_USING_CG_DISPLAY_CREATE_IMAGE_FOR_RECT_BITMAP_DATA
#ifdef SNOOPX_VERBOSE
			NSLog(@"[self drawImage:screenshot At:%@]", NSStringFromRect(NSRectFromCGRect(location)));
#endif
			[self drawImage:screenshot At:location]; // origin is at upper-left
#endif

#ifdef CAPTURE_USING_CG_DISPLAY_CREATE_IMAGE_FOR_RECT_BITMAP_CONTEXT
#ifdef SNOOPX_VERBOSE
			NSLog(@"CGContextDrawImage(context, %@, screenshot)", NSStringFromRect(NSRectFromCGRect(location)));
#endif
			CGContextDrawImage(captureContext, location, screenshot); // origin is at bottom-left
#endif
			CGImageRelease(screenshot);
		}
	}
#ifdef CAPTURE_USING_CG_DISPLAY_CREATE_IMAGE_FOR_RECT_BITMAP_DATA
	if (captureContext)
		CGContextRelease(captureContext);
	captureContext = CGBitmapContextCreate(
		captureData, captureDataWidth, captureDataHeight,
		bitsPerComponent, captureDataBytesPerRow,
		colorSpace, bitmapInfo
	);
#endif

#ifdef CAPTURE_USING_CG_DISPLAY_CREATE_IMAGE_FOR_RECT_BITMAP_CONTEXT
	if (first == YES)
		return nil;
#endif
	return captureContext;
}

- (NSImage*) snoopFrom:(NSArray*)screens
{
	// For HDR displays with ScreenCaptureKit, use direct HDR rendering path
	if (@available(macOS 12.3, *)) {
		if (useScreenCaptureKit && latestPixelBuffer && [self isHDRActive]) {
			NSImage *hdrImage = [self createHDRMagnifiedImage];
			if (hdrImage) {
				// Still need to read cursor color from buffer
				[self captureCursorColor];
				return hdrImage;
			}
			// Fall through to legacy path if HDR image creation fails
		}
	}
	
	// Legacy SDR path
	// capture 
	if ([self getCaptureContextFrom:screens] == nil) {
		NSLog(@"[ERROR] getCaptureContextFrom:screens failed.");
		return nil;
	}

	// zoom
	if ([self getZoomContext] == nil) {
		NSLog(@"[ERROR] getZoomContext failed.");
		return nil;
	}

	// post processing
	[self captureCursorColor];

	// Calculate red square and indicator line positions (matching HDR path logic)
	size_t squareSize = (size_t)(zoom * maxBackingScaleFactor / 2.0);
	if (squareSize < 1) squareSize = 1;

	// Calculate X position: always use geometric center + squareSize/2 adjustment
	size_t geometricX = (zoomPixelWidth / 2) - (squareSize / 2);
	size_t centerZoomedX = geometricX + (squareSize / 2);

	// Quartz Y (bottom-left origin) of the cursor pixel block.
	// The zoom buffer is Y-DOWN: memory row capturePixelCenterY*zoom = zoomPixelHeight/2.
	// In Quartz Y-up: the bottom of that block is at Y = zoomPixelHeight/2 - squareSize.
	// This is correct regardless of crop: getCropImage now uses cropPixelY/2 symmetrically,
	// matching the HDR path's scaling.
	size_t centerZoomedY = zoomPixelHeight / 2 - squareSize;

	// Store positions for graph indicator lines
	// CRITICAL: Indicator should be at same position as red square for all zoom levels
	zoomPixelCenterX = centerZoomedX;
	zoomPixelCenterY = centerZoomedY;
	gridPixelSize = squareSize;

	NSLog(@"[DIAG SDR] capturePixelCenterY=%zu squareSize=%zu centerZoomedY=%zu zoomPixelH=%zu viewPixelH=%zu cropPixelY=%zu",
		capturePixelCenterY, squareSize, centerZoomedY, zoomPixelHeight, viewPixelHeight, cropPixelY);

	[self drawGraph];

	// Statistics are independent of graph mode
	if (statistics) {
		[self drawStatistics];
	}

	if (swatch) [self drawSwatch];

	[self drawCenter];

	// crop
	CGImageRef cropImage = [self getCropImage];
	if (cropImage == nil)
		return nil;

#ifdef SNOOPX_VERBOSE
	{
		size_t cropWidth = CGImageGetWidth(cropImage);
		size_t cropHeight = CGImageGetHeight(cropImage);
		size_t cropBPC = CGImageGetBitsPerComponent(cropImage);
		CGColorSpaceRef cropColorSpace = CGImageGetColorSpace(cropImage);
		CFStringRef cropColorSpaceName = CGColorSpaceCopyName(cropColorSpace);
		NSLog(@"🖼️ SDR cropImage: %zux%zu, %zu bpc, colorSpace=%@", cropWidth, cropHeight, cropBPC, (__bridge NSString*)cropColorSpaceName);
		if (cropColorSpaceName) CFRelease(cropColorSpaceName);
	}
#endif
	
	NSImage *viewImage = [[NSImage alloc] initWithCGImage:cropImage size:NSMakeSize(viewPointWidth, viewPointHeight)];
	CGImageRelease(cropImage);
	return viewImage;
}

// Helper method to detect if cursor moved to a different display
- (void) checkAndSwitchDisplayIfNeeded API_AVAILABLE(macos(12.3)) {
	if (!useScreenCaptureKit || !currentDisplay) {
#ifdef SNOOPX_VERBOSE
		NSLog(@"⚠️ checkAndSwitchDisplay: useScreenCaptureKit=%d, currentDisplay=%p",
			useScreenCaptureKit, currentDisplay);
#endif
		return;
	}

	// Get the display that contains the cursor
	// CRITICAL: NSEvent coordinates are bottom-left origin, but CGGetDisplaysWithPoint
	// expects top-left origin. Need to flip Y coordinate.
	// In AppKit: Y increases upward from bottom
	// In CG: Y increases downward from top
	CGFloat cgY = mainScreenHeight - cursorPointY;
	CGPoint cgCursorPoint = CGPointMake(cursorPointX, cgY);

	CGDirectDisplayID cursorDisplayID = 0;
	uint32_t matchingDisplayCount = 0;
	CGGetDisplaysWithPoint(cgCursorPoint, 1, &cursorDisplayID, &matchingDisplayCount);

	// Only log when display changes or near boundaries for debugging
	static CGDirectDisplayID lastLoggedID = 0;
	if (cursorDisplayID != lastLoggedID) {
		lastLoggedID = cursorDisplayID;
	}

	if (matchingDisplayCount == 0 || cursorDisplayID == 0) {
#ifdef SNOOPX_VERBOSE
		NSLog(@"⚠️ Cursor not on any display?");
#endif
		return;
	}

	// Check if it's the same display we're currently capturing
	if (cursorDisplayID == currentDisplay.displayID) {
		// Same display, no action needed
		return;
	}

#ifdef SNOOPX_VERBOSE
	NSLog(@"🔄 Cursor moved to different display: %u -> %u",
		currentDisplay.displayID, cursorDisplayID);
	NSLog(@"   Restarting capture stream for new display...");
#endif

	// Set target display and restart stream
	// CRITICAL: Dispatch asynchronously to avoid deadlock when called from stream callback
	targetDisplayID = cursorDisplayID;
	dispatch_async(dispatch_get_main_queue(), ^{
		[self stopScreenCaptureKit];
		// Small delay to ensure stream is fully stopped before restarting
		dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
			[self setupScreenCaptureKit]; // This will use targetDisplayID
		});
	});
}

// HDR-preserving version that returns CGImageRef directly (bypasses NSImage conversion)
- (CGImageRef) snoopCGImageFrom:(NSArray*)screens CF_RETURNS_RETAINED
{
	// Check if we need to switch displays
	if (@available(macOS 12.3, *)) {
		[self checkAndSwitchDisplayIfNeeded];
	}

	// For HDR displays with ScreenCaptureKit, return CGImage directly
	if (@available(macOS 12.3, *)) {
		if (useScreenCaptureKit && latestPixelBuffer && [self isHDRActive]) {
			// Still need to read cursor color from buffer
			[self captureCursorColor];
			
			// Get the HDR CGImage directly from the pixel buffer
			dispatch_semaphore_wait(pixelBufferSemaphore, DISPATCH_TIME_FOREVER);
			CVPixelBufferRef buffer = latestPixelBuffer;
			if (buffer) {
				CVPixelBufferRetain(buffer);
			}
			dispatch_semaphore_signal(pixelBufferSemaphore);
			
			if (!buffer) {
				return NULL;
			}
			
			// Calculate which region of the buffer to extract for magnification
			size_t bufferWidth = CVPixelBufferGetWidth(buffer);
			size_t bufferHeight = CVPixelBufferGetHeight(buffer);
			
			CGFloat bufferScaleX = bufferWidth / currentDisplayFrame.size.width;
			CGFloat bufferScaleY = bufferHeight / currentDisplayFrame.size.height;
			
			// Convert capture rect to buffer coordinates
			// CRITICAL: Do all floating-point math first, then round once at pixel conversion
			CGFloat localRectX = capturePointRect.origin.x - currentDisplayFrame.origin.x;
			CGFloat localRectY = capturePointRect.origin.y - currentDisplayFrame.origin.y;
			CGFloat localRectYCG = currentDisplayFrame.size.height - localRectY - capturePointRect.size.height;

			// Round to nearest pixel for best alignment, clamping negatives to 0
			CGFloat rawCaptureX = round(localRectX * bufferScaleX);
			CGFloat rawCaptureY = round(localRectYCG * bufferScaleY);
			if (rawCaptureX < 0) rawCaptureX = 0;
			if (rawCaptureY < 0) rawCaptureY = 0;
			size_t captureX = (size_t)rawCaptureX;
			size_t captureY = (size_t)rawCaptureY;
			size_t captureWidth = (size_t)round(capturePointRect.size.width * bufferScaleX);
			size_t captureHeight = (size_t)round(capturePointRect.size.height * bufferScaleY);

			// Bounds check
			if (captureX >= bufferWidth || captureY >= bufferHeight) {
				CVPixelBufferRelease(buffer);
				return NULL;
			}
			if (captureX + captureWidth > bufferWidth) captureWidth = bufferWidth - captureX;
			if (captureY + captureHeight > bufferHeight) captureHeight = bufferHeight - captureY;
			if (captureWidth == 0 || captureHeight == 0) {
				CVPixelBufferRelease(buffer);
				return NULL;
			}

			// If statistics are enabled, compute HDR statistics from Float16 buffer
			// Do this BEFORE releasing the buffer
			if (statistics) {
				// Compute HDR statistics directly from Float16 values
				[self computeHDRStatisticsFromBuffer:buffer
											   rectX:captureX
											   rectY:captureY
											   width:captureWidth
											  height:captureHeight];
				// Format the statistics string for HDR display
				[self formatStatisticsString];
			}

			NSLog(@"[DIAG HDR] captureX=%zu captureY=%zu captureW=%zu captureH=%zu zoomedH=%zu viewPixelH=%zu capturePixelCenterY=%zu",
				captureX, captureY, captureWidth, captureHeight, captureHeight*zoom, viewPixelHeight, capturePixelCenterY);

			// Create CGImage directly from the pixel buffer region with HDR color space
			CGImageRef hdrImage = [self createHDRCGImageFromBuffer:buffer
															 rectX:captureX
															 rectY:captureY
															 width:captureWidth
															height:captureHeight
															  zoom:zoom];

			CVPixelBufferRelease(buffer);

			// If graphs, statistics, or swatch are enabled, draw them on top of the HDR image
			if (hdrImage && (graph != GSNone || statistics || swatch)) {
				CGImageRef imageWithGraphs = [self drawGraphsOnHDRImage:hdrImage];
				if (imageWithGraphs) {
					CGImageRelease(hdrImage);
					hdrImage = imageWithGraphs; // Transfer ownership
				}
			}

			return hdrImage; // Caller must release (CF_RETURNS_RETAINED)
		}
	}
	
	// Legacy SDR path - wrap in NSImage then extract CGImage
	NSImage *image = [self snoopFrom:screens];
	if (!image) {
		return NULL;
	}
	
	CGImageRef cgImage = [image CGImageForProposedRect:NULL context:nil hints:nil];
	if (cgImage) {
		CGImageRetain(cgImage); // Caller expects retained reference
	}
	
	return cgImage;
}

- (void) drawGraph
{
	switch (graph) {
		case GSNone:
			break;
		case GSHoriStep:
		case GSHoriLinear:
			[self drawHorizontalGraph];
			break;
		case GSVertStep:
		case GSVertLinear:
			[self drawVerticalGraph];
			break;
	}
}

- (void) drawStatistics
{
	size_t size = capturePixelWidth*capturePixelHeight;
	size_t size2 = size/2;

	size_t histogram[3][256] = {{0}};
	size_t peakCount[3] = {0}, peakValue[3] = {0}, maxCount = 0;

	// Initialize instance variable statistics
	mean[0] = mean[1] = mean[2] = 0;
	mediam[0] = mediam[1] = mediam[2] = 0;
	min[0] = min[1] = min[2] = 255;
	max[0] = max[1] = max[2] = 0;

	// statistics {{{
	const unsigned char* row = captureData;
	for (size_t y = 0; y < capturePixelHeight; ++y) {
		const unsigned char* pixel = row;
		for (size_t x = 0; x < capturePixelWidth; ++x) {
			for (size_t c = 0; c < 3; ++c) {
				unsigned char v = pixel[c];
				++histogram[c][v];
				if (histogram[c][v] > peakCount[c]) {
					peakValue[c] = v;
					peakCount[c] = histogram[c][v];
				}
				mean[c] += v;
				if (v > max[c])
					max[c] = v;
				else if (v < min[c])
					min[c] = v;
			}
			pixel += bytesPerPixel;
		}
		row += captureDataBytesPerRow;
	}

	for (size_t c = 0; c < 3; ++c) {
		if (peakCount[c] > maxCount)
			maxCount = peakCount[c];
		mean[c] /= size;
		size_t count = 0;
		for (size_t v = 0; v < 256; ++v) {
			count += histogram[c][v];
			if (count >= size2) {
				mediam[c] = v;
				break;
			}
		}
	}
	// }}}

	// Format the statistics string
	[self formatStatisticsString];

	// Now draw the statistics text in the lower-left corner
	NSString* string = [NSString stringWithUTF8String:statisticsInfoString];
	// Scale font size by backing scale factor for proper Retina rendering
	NSFont* font = [NSFont fontWithName:@"Menlo" size:11.0 * maxBackingScaleFactor];
	NSDictionary* attributes = [NSDictionary
		dictionaryWithObjectsAndKeys:font, NSFontAttributeName,
		[NSColor whiteColor], NSForegroundColorAttributeName,
		nil];

	// Calculate text size (returns size in pixels since font is scaled)
	NSSize textSize = [string sizeWithAttributes:attributes];
	const size_t spacing = 2 * maxBackingScaleFactor;
	CGRect textRect = CGRectMake(
		SPACING * maxBackingScaleFactor + spacing * 2,
		SPACING * maxBackingScaleFactor * 2 + spacing,
		textSize.width + 4*spacing,
		textSize.height + 2*spacing
	);

	// Draw black background box
	CGContextSetBlendMode(zoomContext, kCGBlendModeNormal);
	CGContextSetRGBFillColor(zoomContext, 0.0, 0.0, 0.0, 1.0);
	CGContextFillRect(zoomContext, textRect);

	// Draw white border
	CGContextSetRGBStrokeColor(zoomContext, 1.0, 1.0, 1.0, 1.0);
	CGContextSetLineWidth(zoomContext, maxBackingScaleFactor);
	CGContextStrokeRect(zoomContext, textRect);

	// Draw text using NSGraphicsContext
	NSGraphicsContext *nsContext = [NSGraphicsContext graphicsContextWithCGContext:zoomContext flipped:NO];
	[NSGraphicsContext saveGraphicsState];
	[NSGraphicsContext setCurrentContext:nsContext];

	CGPoint textPos = CGPointMake(
		SPACING * maxBackingScaleFactor + spacing * 4,
		SPACING * maxBackingScaleFactor * 2 + spacing * 2
	);
	[string drawAtPoint:textPos withAttributes:attributes];

	[NSGraphicsContext restoreGraphicsState];
}

- (void) formatStatisticsString
{
	// Format statistics string based on whether we're in HDR or SDR mode
	BOOL isHDR = [self isHDRActive];

	if (isHDR) {
		// HDR formatting: all values as f-stops (floating point 0.0-5.0+)
		sprintf(statisticsInfoString,
			"min=(%.3f,%.3f,%.3f), max=(%.3f,%.3f,%.3f)\navg=(%.2f,%.2f,%.2f), med=(%.2f,%.2f,%.2f)",
			hdrMinR, hdrMinG, hdrMinB,
			hdrMaxR, hdrMaxG, hdrMaxB,
			hdrAvgR, hdrAvgG, hdrAvgB,
			hdrMedR, hdrMedG, hdrMedB
		);
	} else {
		// SDR formatting: everything in 0-255 range
		// Use the computed values from drawStatistics
		sprintf(statisticsInfoString,
			"min=(%3zu,%3zu,%3zu), max=(%3zu,%3zu,%3zu)\n"
			"avg=(%3zu,%3zu,%3zu), med=(%3zu,%3zu,%3zu)",
			min[RED_INDEX], min[GREEN_INDEX], min[BLUE_INDEX],
			max[RED_INDEX], max[GREEN_INDEX], max[BLUE_INDEX],
			mean[RED_INDEX], mean[GREEN_INDEX], mean[BLUE_INDEX],
			mediam[RED_INDEX], mediam[GREEN_INDEX], mediam[BLUE_INDEX]
		);
	}
}

- (void) drawSwatch
{
	// Match statistics box height using same font
	NSFont* font = [NSFont fontWithName:@"Menlo" size:11.0 * maxBackingScaleFactor];
	NSDictionary* attrs = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
	NSSize textSize = [@"X\nX" sizeWithAttributes:attrs];
	const CGFloat innerSpacing = 2 * maxBackingScaleFactor;
	CGFloat swatchSize = textSize.height + 2 * innerSpacing;

	CGFloat margin = SPACING * maxBackingScaleFactor + innerSpacing;
	CGFloat swatchX, swatchY;
	if (graph == GSVertStep || graph == GSVertLinear) {
		// upper-left corner (CGContext: lower-left origin, Y increases up)
		swatchX = margin;
		swatchY = viewPixelHeight - margin - swatchSize;
	} else {
		// lower-right corner
		swatchX = viewPixelWidth - margin - swatchSize;
		swatchY = margin;
	}

	CGRect swatchRect = CGRectMake(swatchX, swatchY, swatchSize, swatchSize);
	// Black fill + white outer border
	[self drawGraphCanvasWithRect:swatchRect];
	// Color fill inset by one border width (exposes black inner boundary)
	CGContextSetBlendMode(zoomContext, kCGBlendModeNormal);
	CGContextSetRGBFillColor(zoomContext,
		cursorRed   / 255.0,
		cursorGreen / 255.0,
		cursorBlue  / 255.0, 1.0);
	CGContextFillRect(zoomContext,
		CGRectInset(swatchRect, maxBackingScaleFactor, maxBackingScaleFactor));
}

- (void) drawCenter
{
	if (zoom*maxBackingScaleFactor <= 2)
		return;

	// Red square position matches indicator position exactly now (simplified)
	size_t squareSize = gridPixelSize;
	size_t centerZoomedX = zoomPixelCenterX;
	size_t centerZoomedY = zoomPixelCenterY;

	CGRect rect = {{centerZoomedX, centerZoomedY}, {squareSize, squareSize}};
#ifdef SNOOPX_VERBOSE
	NSLog(@"drawCenter: %@", NSStringFromRect(rect));
#endif
	CGContextSetBlendMode(zoomContext, kCGBlendModeNormal);
	CGContextSetRGBStrokeColor(zoomContext, 1.0, 0.0, 0.0, 1.0); // Red
	CGContextStrokeRectWithWidth(zoomContext, rect, maxBackingScaleFactor);
}

- (void) drawHorizontalGraph
{
	size_t spacing = SPACING*maxBackingScaleFactor;
	CGRect rect = {
		{spacing, zoomPixelHeight - spacing},
		{viewPixelWidth - 2*spacing, viewPixelHeight/3}
	};
	if (rect.size.height > 255*maxBackingScaleFactor)
		rect.size.height = 255*maxBackingScaleFactor;
	rect.origin.y -= rect.size.height;

	[self drawGraphCanvasWithRect:rect];

	// highlight - primary indicator line (horizontal line across entire width)
	if (highlight) {
		CGContextSetBlendMode(zoomContext, kCGBlendModeNormal);
		CGRect hlRect = {
			{0, zoomPixelCenterY},
			{viewPixelWidth, gridPixelSize}
		};
		CGContextSetRGBFillColor(zoomContext, 1.0, 1.0, 1.0, 0.15); // White with 15% alpha
		CGContextFillRect(zoomContext, hlRect);
	}

	CGContextSetBlendMode(zoomContext, kCGBlendModeLighten);
	CGColorRef colors[3] = {cgBlue, cgGreen, cgRed};
	const unsigned char* row = captureData + capturePixelCenterY*captureDataBytesPerRow;
	// Max offset so the segment/line stays just inside the top border (fully visible at 255)
	size_t hMaxOffset = (size_t)rect.size.height - (size_t)maxBackingScaleFactor;
	if (graph == GSHoriStep) {
		for (size_t x = rect.origin.x; x <= rect.origin.x + rect.size.width; ++x) {
			const unsigned char* pixel = row + (x/zoom)*bytesPerPixel;
			for (size_t c = 0; c < 3; ++c) {
				size_t y = rect.size.height*pixel[c]/255;
				if (y > hMaxOffset) y = hMaxOffset;
				CGRect stepRect = {
					{x, rect.origin.y + y},
					{1, maxBackingScaleFactor}
				};
				CGContextSetFillColorWithColor(zoomContext, colors[c]);
				CGContextFillRect(zoomContext, stepRect);
			}
		}
	} else {
		size_t realZoom = zoom/maxBackingScaleFactor;
		if (realZoom == 0)
			realZoom = 1;

		CGContextSetLineWidth(zoomContext, maxBackingScaleFactor);
		for (size_t c = 0; c < 3; ++c) {
			const unsigned char* pixel = row + (size_t)(rect.origin.x/zoom)*bytesPerPixel;
			CGContextSetStrokeColorWithColor(zoomContext, colors[c]);
			size_t startOff = (size_t)(rect.size.height*pixel[c]/255);
			if (startOff > hMaxOffset) startOff = hMaxOffset;
			CGContextMoveToPoint(zoomContext, rect.origin.x, rect.origin.y + startOff);

			size_t x = rect.origin.x + (size_t)1.5*(realZoom) - (size_t)rect.origin.x % realZoom;
			for (; x <= rect.origin.x + rect.size.width; x += realZoom) {
				pixel = row + (x/zoom)*bytesPerPixel;
				size_t off = rect.size.height*pixel[c]/255;
				if (off > hMaxOffset) off = hMaxOffset;
				CGContextAddLineToPoint(zoomContext, x, rect.origin.y + off);
			}
			x -= realZoom;
			if (x < rect.origin.x + rect.size.width) {
				pixel = row + ((size_t)(rect.origin.x + rect.size.width)/zoom)*bytesPerPixel;
				size_t off = rect.size.height*pixel[c]/255;
				if (off > hMaxOffset) off = hMaxOffset;
				CGContextAddLineToPoint(zoomContext, x, rect.origin.y + off);
			}
			CGContextStrokePath(zoomContext);
		}
	}

	// Draw secondary indicator line - a vertical line through the graph canvas
	// showing which column is being sampled (stops before canvas boundaries)
	if (highlight) {
		CGContextSetBlendMode(zoomContext, kCGBlendModeNormal);
		size_t borderWidth = maxBackingScaleFactor;
		CGRect indicatorRect = {
			{zoomPixelCenterX, rect.origin.y + borderWidth},
			{gridPixelSize, rect.size.height - 2*borderWidth}
		};
		CGContextSetRGBFillColor(zoomContext, 1.0, 1.0, 1.0, 0.1125); // White with 11.25% alpha (midpoint between secondary and primary)
		CGContextFillRect(zoomContext, indicatorRect);
	}
}

- (void) drawVerticalGraph
{
	size_t spacing = SPACING*maxBackingScaleFactor;
	CGRect rect = {
		{viewPixelWidth - spacing, cropPixelY + spacing},
		{viewPixelWidth/3, viewPixelHeight - 2*spacing}
	};
	if (rect.size.width > 255*maxBackingScaleFactor)
		rect.size.width = 255*maxBackingScaleFactor;
	rect.origin.x -= rect.size.width;
	[self drawGraphCanvasWithRect:rect];

	// highlight - primary indicator line (vertical line through entire height)
	if (highlight) {
		CGContextSetBlendMode(zoomContext, kCGBlendModeNormal);
		CGRect hlRect = {{zoomPixelCenterX, 0}, {gridPixelSize, viewPixelHeight}};
		CGContextSetRGBFillColor(zoomContext, 1.0, 1.0, 1.0, 0.15); // White with 15% alpha
		CGContextFillRect(zoomContext, hlRect);
	}

	CGContextSetBlendMode(zoomContext, kCGBlendModeLighten);
	CGColorRef colors[3] = {cgBlue, cgGreen, cgRed};
	const unsigned char* row = captureData + capturePixelCenterX*bytesPerPixel;
	size_t vMaxOffset = (size_t)rect.size.width - (size_t)maxBackingScaleFactor;
	if (graph == GSVertStep) {
		for (size_t y = rect.origin.y; y <= rect.origin.y + rect.size.height; ++y) {
			// Direct pixel copy: zoom buffer row y → captureData row y/zoom
			const unsigned char* pixel = row + (y/zoom)*captureDataBytesPerRow;
			for (size_t c = 0; c < 3; ++c) {
				size_t x = rect.size.width*pixel[c]/255;
				if (x > vMaxOffset) x = vMaxOffset;
				CGRect stepRect = {
					{rect.origin.x + x, y},
					{maxBackingScaleFactor, 1}
				};
				CGContextSetFillColorWithColor(zoomContext, colors[c]);
				CGContextFillRect(zoomContext, stepRect);
			}
		}
	} else {
		size_t realZoom = zoom/maxBackingScaleFactor;
		if (realZoom == 0)
			realZoom = 1;

		CGContextSetLineWidth(zoomContext, maxBackingScaleFactor);
		for (size_t c = 0; c < 3; ++c) {
			const unsigned char* pixel = row + (size_t)(rect.origin.y/zoom)*captureDataBytesPerRow;
			CGContextSetStrokeColorWithColor(zoomContext, colors[c]);
			size_t startOff = (size_t)(rect.size.width*pixel[c]/255);
			if (startOff > vMaxOffset) startOff = vMaxOffset;
			CGContextMoveToPoint(zoomContext, rect.origin.x + startOff, rect.origin.y);

			size_t y = rect.origin.y + (size_t)1.5*(realZoom) - (size_t)rect.origin.y % realZoom;
			for (; y <= rect.origin.y + rect.size.height; y += realZoom) {
				pixel = row + ((size_t)(y/zoom))*captureDataBytesPerRow;
				size_t xOff = rect.size.width*pixel[c]/255;
				if (xOff > vMaxOffset) xOff = vMaxOffset;
				CGContextAddLineToPoint(zoomContext, rect.origin.x + xOff, y);
			}
			y -= realZoom;
			if (y < rect.origin.y + rect.size.height) {
				pixel = row + ((size_t)(rect.origin.y + rect.size.height)/zoom)*captureDataBytesPerRow;
				size_t xOff = rect.size.width*pixel[c]/255;
				if (xOff > vMaxOffset) xOff = vMaxOffset;
				CGContextAddLineToPoint(zoomContext, rect.origin.x + xOff, y);
			}
			CGContextStrokePath(zoomContext);
		}
	}

	// Draw secondary indicator line - a horizontal line through the graph canvas
	// showing which row is being sampled (stops before canvas boundaries)
	if (highlight) {
		CGContextSetBlendMode(zoomContext, kCGBlendModeNormal);
		size_t borderWidth = maxBackingScaleFactor;
		CGRect indicatorRect = {
			{rect.origin.x + borderWidth, zoomPixelCenterY},
			{rect.size.width - 2*borderWidth, gridPixelSize}
		};
		CGContextSetRGBFillColor(zoomContext, 1.0, 1.0, 1.0, 0.1125); // White with 11.25% alpha (midpoint between secondary and primary)
		CGContextFillRect(zoomContext, indicatorRect);
	}
}

- (void) drawGraphCanvasWithRect:(CGRect)rect
{
	// black blackground
	CGContextSetBlendMode(zoomContext, kCGBlendModeNormal);
	CGContextSetFillColorWithColor(zoomContext, cgBlack);
	CGContextFillRect(zoomContext, rect);

	// white border
	CGRect borderRect = {
		{rect.origin.x - maxBackingScaleFactor, rect.origin.y - maxBackingScaleFactor},
		{rect.size.width + 2*maxBackingScaleFactor, rect.size.height + 2*maxBackingScaleFactor}
	};
	CGContextSetStrokeColorWithColor(zoomContext, cgWhite);
	CGContextStrokeRectWithWidth(zoomContext, borderRect, maxBackingScaleFactor);
}

/*
- (unsigned char*) getNormalizedBitmapDataFrom:(CGImageRef)image
{
	CGImageAlphaInfo ai = CGImageGetAlphaInfo(image);
	CGBitmapInfo bi = CGImageGetBitmapInfo(image);
	size_t bpc = CGImageGetBitsPerComponent(image);
	size_t bpp = CGImageGetBitsPerPixel(image);
	size_t bpr = CGImageGetBytesPerRow(image);
	size_t width = CGImageGetWidth(image);
	size_t height = CGImageGetHeight(image);

	if (bitmapInfo == bi) {
		CFDataRef cfData = CGDataProviderCopyData(CGImageGetDataProvider(image));
		NSData* nsData = (__bridge NSData*)cfData;
		*data = (const unsigned char*)CFDataGetBytePtr(cfData);
		return 
	}

	switch (alphaInfo) {
	case kCGImageAlphaNone:
	case kCGImageAlphaOnly :
		// unsupported format
		return NULL;
	case kCGImageAlphaPremultipliedLast:
	case kCGImageAlphaLast:
	case kCGImageAlphaNoneSkipLast:
		break;
	case kCGImageAlphaPremultipliedFirst:
	case kCGImageAlphaFirst:
	case kCGImageAlphaNoneSkipFirst:
		break;
	}
}
*/

- (void) drawImage:(CGImageRef)image At:(CGRect)rect
{
	CGBitmapInfo bi = CGImageGetBitmapInfo(image);
	if (bitmapInfo == bi) {
		CFDataRef cfData = CGDataProviderCopyData(CGImageGetDataProvider(image));
		const unsigned char *data = (const unsigned char*)CFDataGetBytePtr(cfData);
		drawBitmapData(
			data,
			CGImageGetWidth(image), CGImageGetHeight(image),
			CGImageGetBytesPerRow(image),
			captureData,
			capturePixelWidth, capturePixelHeight,
			captureDataBytesPerRow, bytesPerPixel,
			rect.origin.x, rect.origin.y, rect.size.width, rect.size.height
		);
		CFRelease(cfData);
	} else {
		NSLog(@"❌ BITMAP INFO MISMATCH - capture will be black! Need to implement conversion.");
		// TODO: to be implemented!
	}
}

- (void) setMainScreenHeight:(size_t)height
{
	mainScreenHeight = height;
}

- (void) setMaxBackingScaleFactor:(CGFloat)factor
{
	if (factor != maxBackingScaleFactor) {
#ifdef SNOOPX_VERBOSE
		NSLog(@"maxBackingScaleFactor: %f", maxBackingScaleFactor);
#endif
		maxBackingScaleFactor = factor;
		[self resetPixelSizes];
	}
}

- (NSRect) getCurrentDisplayFrame
{
	return currentDisplayFrame;
}

- (void) setHDRCapable:(BOOL)capable
{
	if (isHDRCapable != capable) {
		isHDRCapable = capable;
		useHDRCapture = capable;
		
		// Try to use ScreenCaptureKit for HDR if available (macOS 12.3+)
		if (@available(macOS 12.3, *)) {
			if (capable && !useScreenCaptureKit) {
				for (NSScreen *screen in [NSScreen screens]) {
					CGFloat potential = screen.maximumPotentialExtendedDynamicRangeColorComponentValue;
					CGFloat current   = screen.maximumExtendedDynamicRangeColorComponentValue;
					NSLog(@"🔆 HDR headroom — potential: %.2f (%.2f stops), current: %.2f (%.2f stops) [%@]",
						  potential, log2(potential), current, log2(current), screen.localizedName);
				}
#ifdef SNOOPX_VERBOSE
				NSLog(@"📱 HDR display detected - initializing ScreenCaptureKit");
#endif
				// Detect which display the cursor is on for initial setup
				// Convert from AppKit (bottom-left) to CG (top-left) coordinates
				CGFloat cgY = mainScreenHeight - cursorPointY;
				CGPoint cgCursorPoint = CGPointMake(cursorPointX, cgY);
				CGDirectDisplayID cursorDisplayID = 0;
				uint32_t matchingDisplayCount = 0;
				CGGetDisplaysWithPoint(cgCursorPoint, 1, &cursorDisplayID, &matchingDisplayCount);
				if (matchingDisplayCount > 0 && cursorDisplayID != 0) {
					targetDisplayID = cursorDisplayID;
				}

				[self setupScreenCaptureKit];
			} else if (!capable && useScreenCaptureKit) {
#ifdef SNOOPX_VERBOSE
				NSLog(@"📱 HDR disabled - stopping ScreenCaptureKit");
#endif
				[self stopScreenCaptureKit];
			}
		}
		
		// Always use standard RGB for display contexts
		// We'll convert from HDR when using ScreenCaptureKit
		if (colorSpace) {
			CGColorSpaceRelease(colorSpace);
		}
		
		colorSpace = CGColorSpaceCreateDeviceRGB();
		bitsPerComponent = 8;
		bitsPerPixel = bitsPerComponent * 4;
		bytesPerPixel = bitsPerPixel / 8;
		alphaInfo = kCGImageAlphaPremultipliedFirst;
		bitmapInfo = kCGBitmapByteOrder32Little | alphaInfo;
		
		// Force recreation of contexts
		if (captureContext) {
			CGContextRelease(captureContext);
			captureContext = nil;
		}
		if (zoomContext) {
			CGContextRelease(zoomContext);
			zoomContext = nil;
		}
		if (viewContext) {
			CGContextRelease(viewContext);
			viewContext = nil;
		}
	}
}

- (void) setDisplayMode:(DisplayMode)mode
{
	displayMode = mode;
#ifdef SNOOPX_VERBOSE
	NSLog(@"[Snooper setDisplayMode:%d] (0=Auto, 1=SDR, 2=HDR)", mode);
#endif
}

- (BOOL) isHDRActive
{
	// Check display mode setting
	switch (displayMode) {
		case DisplayModeSDR:
			// Force SDR mode
			return NO;
		case DisplayModeHDR:
			// Force HDR mode (if capable)
			return isHDRCapable && useScreenCaptureKit && (latestPixelBuffer != NULL);
		case DisplayModeAuto:
		default:
			// Auto: use HDR if capable, using ScreenCaptureKit, AND have received at least one frame
			return isHDRCapable && useScreenCaptureKit && (latestPixelBuffer != NULL);
	}
}

#pragma mark - ScreenCaptureKit Methods

// Create an HDR-capable magnified image directly from the pixel buffer
- (NSImage*) createHDRMagnifiedImage API_AVAILABLE(macos(12.3)) {
	dispatch_semaphore_wait(pixelBufferSemaphore, DISPATCH_TIME_FOREVER);
	CVPixelBufferRef buffer = latestPixelBuffer;
	if (buffer) {
		CVPixelBufferRetain(buffer);
	}
	dispatch_semaphore_signal(pixelBufferSemaphore);
	
	if (!buffer) {
		return nil;
	}
	
	// Calculate which region of the buffer to extract for magnification
	size_t bufferWidth = CVPixelBufferGetWidth(buffer);
	size_t bufferHeight = CVPixelBufferGetHeight(buffer);
	
	CGFloat bufferScaleX = bufferWidth / currentDisplayFrame.size.width;
	CGFloat bufferScaleY = bufferHeight / currentDisplayFrame.size.height;

	// Convert capture rect to buffer coordinates
	// CRITICAL: Do all floating-point math FIRST, then floor only once at pixel conversion
	// Flooring intermediate values compounds rounding errors
	CGFloat localRectX = capturePointRect.origin.x - currentDisplayFrame.origin.x;
	CGFloat localRectY = capturePointRect.origin.y - currentDisplayFrame.origin.y;
	CGFloat localRectYCG = currentDisplayFrame.size.height - localRectY - capturePointRect.size.height;

	// Round to nearest pixel for best alignment, clamping negatives to 0
	CGFloat rawCaptureX = round(localRectX * bufferScaleX);
	CGFloat rawCaptureY = round(localRectYCG * bufferScaleY);
	if (rawCaptureX < 0) rawCaptureX = 0;
	if (rawCaptureY < 0) rawCaptureY = 0;
	size_t captureX = (size_t)rawCaptureX;
	size_t captureY = (size_t)rawCaptureY;
	size_t captureWidth = (size_t)round(capturePointRect.size.width * bufferScaleX);
	size_t captureHeight = (size_t)round(capturePointRect.size.height * bufferScaleY);

	// Bounds check
	if (captureX >= bufferWidth || captureY >= bufferHeight) {
		CVPixelBufferRelease(buffer);
		return nil;
	}
	if (captureX + captureWidth > bufferWidth) captureWidth = bufferWidth - captureX;
	if (captureY + captureHeight > bufferHeight) captureHeight = bufferHeight - captureY;
	if (captureWidth == 0 || captureHeight == 0) {
		CVPixelBufferRelease(buffer);
		return nil;
	}

	// Create CGImage directly from the pixel buffer region with HDR color space
	CGImageRef hdrImage = [self createHDRCGImageFromBuffer:buffer
													 rectX:captureX
													 rectY:captureY
													 width:captureWidth
													height:captureHeight
													  zoom:zoom];
	
	CVPixelBufferRelease(buffer);
	
	if (!hdrImage) {
		return nil;
	}
	
	// Wrap in NSImage with proper size and DISABLE interpolation for pixel-perfect rendering
	NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize(viewPointWidth, viewPointHeight)];
	NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCGImage:hdrImage];
	
	// CRITICAL: Disable interpolation to prevent anti-aliasing/resampling
	// This ensures pixel-perfect nearest-neighbor rendering
	[rep setPixelsHigh:viewPixelHeight];
	[rep setPixelsWide:viewPixelWidth];
	
	[image addRepresentation:rep];
	[rep release];
	CGImageRelease(hdrImage);
	
	return image;
}

// Helper method to draw graphs on top of an HDR CGImage
- (CGImageRef) drawGraphsOnHDRImage:(CGImageRef)sourceImage CF_RETURNS_RETAINED API_AVAILABLE(macos(10.12)) {
	if (!sourceImage) return nil;

	size_t width = CGImageGetWidth(sourceImage);
	size_t height = CGImageGetHeight(sourceImage);

	// Create a bitmap context to draw into
	// Use Extended Display P3 to match the source image's color space (set by createHDRCGImageFromBuffer)
	// This avoids color conversion when drawing the source and ensures swatch fill color
	// (raw P3 values from the pixel buffer) is interpreted correctly.
	CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedDisplayP3);
	if (!colorSpace) {
		colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
	}

	CGContextRef ctx = CGBitmapContextCreate(
		NULL, width, height,
		16, // 16 bits per component (Float16)
		width * 8, // bytesPerRow (4 channels × 2 bytes)
		colorSpace,
		kCGImageAlphaPremultipliedLast | kCGBitmapFloatComponents | kCGBitmapByteOrder16Little
	);

	if (!ctx) {
		NSLog(@"⚠️ Failed to create bitmap context for graph drawing");
		CGColorSpaceRelease(colorSpace);
		return CGImageRetain(sourceImage); // Return original
	}

	// Draw the source HDR image into the context
	CGContextDrawImage(ctx, CGRectMake(0, 0, width, height), sourceImage);

	// Draw indicator lines only (canvas and plots are now in Float16 buffer)
	if (graph == GSHoriStep || graph == GSHoriLinear) {
		// Horizontal graph at top - plotting a ROW of pixels
		// Draw horizontal indicator line through red square
		// This shows which row is being plotted
		if (highlight) {
			// Use white with transparency for visibility over all content
			CGContextSetRGBFillColor(ctx, 1.0, 1.0, 1.0, 0.15); // White with 15% alpha
			CGFloat lineY = (CGFloat)zoomPixelCenterY;
			CGFloat lineHeight = (CGFloat)gridPixelSize;
			// Draw across the captured content width (matches SDR behavior)
			CGContextFillRect(ctx, CGRectMake(0, lineY, width, lineHeight));
		}

		// Draw secondary indicator line - a vertical line through the graph canvas
		// showing which column is being sampled (stops before canvas boundaries)
		CGFloat spacing = SPACING * 2.0;
		CGFloat canvasHeight = height / 3;
		CGFloat canvasTop = spacing;  // In Float16 buffer coords (top-left origin)

		if (highlight) {
			CGFloat canvasBottom = canvasTop + canvasHeight;
			CGFloat lineX = (CGFloat)zoomPixelCenterX;
			CGFloat lineWidth = (CGFloat)gridPixelSize;
			CGFloat borderWidth = 2.0; // Border line width
			// Draw vertical line through canvas height only, inside borders
			// Convert to CGContext coords (bottom-left origin)
			CGContextSetRGBFillColor(ctx, 1.0, 1.0, 1.0, 0.1125); // White with 11.25% alpha (midpoint between secondary and primary)
			CGContextFillRect(ctx, CGRectMake(lineX, height - canvasBottom + borderWidth, lineWidth, canvasHeight - 2*borderWidth));
		}

		// Draw HDR reference line labels if using 4.0 scale
		if (useHDRScaleForGraphs) {
			spacing = SPACING * 2.0;
			canvasHeight = height / 3;
			canvasTop = spacing;  // In Float16 buffer coords (top-left origin)

			// Create NSGraphicsContext from CGContext for text drawing
			NSGraphicsContext *nsContext = [NSGraphicsContext graphicsContextWithCGContext:ctx flipped:NO];
			[NSGraphicsContext saveGraphicsState];
			[NSGraphicsContext setCurrentContext:nsContext];

			NSFont *font = [NSFont systemFontOfSize:13.0];
			NSDictionary *attributes = @{
				NSFontAttributeName: font,
				NSForegroundColorAttributeName: [NSColor colorWithWhite:0.6 alpha:1.0]
			};

			// Draw labels for +1.0, +2.0, +3.0 just below reference lines
			for (float refValue = 1.0f; refValue < 4.0f; refValue += 1.0f) {
				// Calculate Y position in Float16 buffer coords (top-left origin)
				// Line is at: canvasTop + canvasHeight - (canvasHeight * refValue / 4.0)
				CGFloat lineY_buffer = canvasTop + canvasHeight - (canvasHeight * refValue / 4.0f);
				// Convert to CGContext coords (bottom-left origin)
				CGFloat lineY = height - lineY_buffer;

				NSString *label = [NSString stringWithFormat:@"+%.1f", refValue];

				// Draw label just below the line, near left edge of canvas
				CGPoint labelPos = CGPointMake(spacing + 4, lineY - 14);
				[label drawAtPoint:labelPos withAttributes:attributes];
			}

			// Draw +4.0 label at top of canvas
			CGFloat canvasTopY = height - canvasTop;
			NSString *topLabel = @"+4.0";
			CGPoint topLabelPos = CGPointMake(spacing + 4, canvasTopY - 16);
			[topLabel drawAtPoint:topLabelPos withAttributes:attributes];

			[NSGraphicsContext restoreGraphicsState];
		}
	} else if (graph == GSVertStep || graph == GSVertLinear) {
		// Vertical graph at right - plotting a COLUMN of pixels
		// Draw vertical indicator line through red square
		// This shows which column is being plotted
		if (highlight) {
			// Use white with transparency for visibility over all content
			CGContextSetRGBFillColor(ctx, 1.0, 1.0, 1.0, 0.15); // White with 15% alpha
			CGFloat lineX = (CGFloat)zoomPixelCenterX;
			CGFloat lineWidth = (CGFloat)gridPixelSize;
			// Draw through the captured content height (matches SDR behavior)
			CGContextFillRect(ctx, CGRectMake(lineX, 0, lineWidth, height));
		}

		// Draw secondary indicator line - a horizontal line through the graph canvas
		// showing which row is being sampled (stops before canvas boundaries)
		CGFloat spacing = SPACING * 2.0;
		CGFloat canvasWidth = width / 3;
		CGFloat canvasLeft = width - spacing - canvasWidth;

		if (highlight) {
			CGFloat lineY = (CGFloat)zoomPixelCenterY;
			CGFloat lineHeight = (CGFloat)gridPixelSize;
			CGFloat borderWidth = 2.0; // Border line width
			// Draw horizontal line through canvas width only, inside borders
			CGContextSetRGBFillColor(ctx, 1.0, 1.0, 1.0, 0.1125); // White with 11.25% alpha (midpoint between secondary and primary)
			CGContextFillRect(ctx, CGRectMake(canvasLeft + borderWidth, lineY, canvasWidth - 2*borderWidth, lineHeight));
		}

		// Draw HDR reference line labels if using 4.0 scale
		if (useHDRScaleForGraphs) {
			spacing = SPACING * 2.0;
			canvasWidth = width / 3;
			canvasLeft = width - spacing - canvasWidth;
			CGFloat canvasTop = spacing;  // In Float16 buffer coords (top-left origin)

			// Create NSGraphicsContext from CGContext for text drawing
			NSGraphicsContext *nsContext = [NSGraphicsContext graphicsContextWithCGContext:ctx flipped:NO];
			[NSGraphicsContext saveGraphicsState];
			[NSGraphicsContext setCurrentContext:nsContext];

			NSFont *font = [NSFont systemFontOfSize:13.0];
			NSDictionary *attributes = @{
				NSFontAttributeName: font,
				NSForegroundColorAttributeName: [NSColor colorWithWhite:0.6 alpha:1.0]
			};

			// Draw labels for +1.0, +2.0, +3.0 just to the left of reference lines
			CGFloat canvasTopY = height - canvasTop;
			for (float refValue = 1.0f; refValue < 4.0f; refValue += 1.0f) {
				// Calculate X position - same in both coordinate systems
				CGFloat lineX = canvasLeft + (canvasWidth * (refValue / 4.0f));
				NSString *label = [NSString stringWithFormat:@"+%.1f", refValue];

				// Draw label just to the left of the line, slightly below top to avoid clipping
				CGPoint labelPos = CGPointMake(lineX - 32, canvasTopY - 20);
				[label drawAtPoint:labelPos withAttributes:attributes];
			}

			// Draw +4.0 label at right edge of canvas
			CGFloat canvasRightX = canvasLeft + canvasWidth;
			NSString *rightLabel = @"+4.0";
			CGPoint rightLabelPos = CGPointMake(canvasRightX - 32, canvasTopY - 20);
			[rightLabel drawAtPoint:rightLabelPos withAttributes:attributes];

			[NSGraphicsContext restoreGraphicsState];
		}
	}

	// Statistics are independent of graph mode
	if (statistics) {
		// Draw statistics text overlay
		NSString* string = [self createStatisticsInfoString];
		// Use larger font for HDR (22.0 instead of 11.0) to match SDR size
		NSFont* font = [NSFont fontWithName:@"Menlo" size:22.0];
		NSDictionary* attributes = [NSDictionary
			dictionaryWithObjectsAndKeys:font, NSFontAttributeName,
			[NSColor whiteColor], NSForegroundColorAttributeName,
			nil];

		// Calculate text bounding rect
		NSSize textSize = [string sizeWithAttributes:attributes];
		const size_t spacing = 4; // Doubled spacing for larger text
		CGRect textRect = CGRectMake(
			SPACING * 2.0 + spacing * 2,
			SPACING * 2.0 + spacing,
			textSize.width + 4*spacing,
			textSize.height + 2*spacing
		);

		// Draw black background box
		CGContextSetRGBFillColor(ctx, 0.0, 0.0, 0.0, 1.0);
		CGContextFillRect(ctx, textRect);

		// Draw white border
		CGContextSetRGBStrokeColor(ctx, 1.0, 1.0, 1.0, 1.0);
		CGContextSetLineWidth(ctx, 1.0);
		CGContextStrokeRect(ctx, textRect);

		// Draw text using NSGraphicsContext
		NSGraphicsContext *nsContext = [NSGraphicsContext graphicsContextWithCGContext:ctx flipped:NO];
		[NSGraphicsContext saveGraphicsState];
		[NSGraphicsContext setCurrentContext:nsContext];

		CGPoint textPos = CGPointMake(
			SPACING * 2.0 + spacing * 4,
			SPACING * 2.0 + spacing * 2
		);
		[string drawAtPoint:textPos withAttributes:attributes];

		[NSGraphicsContext restoreGraphicsState];
		[string release];
	}

	if (swatch) {
		NSFont* font = [NSFont fontWithName:@"Menlo" size:22.0];
		NSDictionary* attrs = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
		NSSize textSize = [@"X\nX" sizeWithAttributes:attrs];
		const CGFloat spacing = 4;
		CGFloat swatchSize = textSize.height + 2 * spacing;
		CGFloat margin = SPACING * 2.0 + spacing * 2;

		CGFloat swatchX, swatchY;
		if (graph == GSVertStep || graph == GSVertLinear) {
			swatchX = margin;
			swatchY = height - margin - swatchSize;
		} else {
			swatchX = width - margin - swatchSize;
			swatchY = margin;
		}

		CGRect swatchRect = CGRectMake(swatchX, swatchY, swatchSize, swatchSize);
		CGFloat lineWidth = 1.0;

		// Black fill
		CGContextSetRGBFillColor(ctx, 0.0, 0.0, 0.0, 1.0);
		CGContextFillRect(ctx, swatchRect);
		// White outer border
		CGRect borderRect = CGRectMake(
			swatchRect.origin.x - lineWidth, swatchRect.origin.y - lineWidth,
			swatchRect.size.width + 2*lineWidth, swatchRect.size.height + 2*lineWidth);
		CGContextSetRGBStrokeColor(ctx, 1.0, 1.0, 1.0, 1.0);
		CGContextStrokeRectWithWidth(ctx, borderRect, lineWidth);
		// Swatch color fill — inset to show black inner boundary; HDR values > 1.0 preserved
		// Use CGColorCreate instead of CGContextSetRGBFillColor because the latter
		// clamps components to 0.0–1.0, losing HDR values above 1.0.
		{
			CGFloat components[] = {cursorRedHDR, cursorGreenHDR, cursorBlueHDR, 1.0};
			CGColorRef fillColor = CGColorCreate(colorSpace, components);
			CGContextSetFillColorWithColor(ctx, fillColor);
			CGColorRelease(fillColor);
		}
		CGContextFillRect(ctx, CGRectInset(swatchRect, lineWidth, lineWidth));
	}

	// Create CGImage from the context
	CGImageRef result = CGBitmapContextCreateImage(ctx);

	CGContextRelease(ctx);
	CGColorSpaceRelease(colorSpace);

	return result; // Caller must release
}

// Draw RGB plot lines into Float16 buffer
- (void) drawGraphsIntoFloat16Buffer:(uint16_t*)pixels
							   width:(size_t)width
							  height:(size_t)height
						 bytesPerRow:(size_t)bytesPerRow
								zoom:(size_t)zoomFactor {
	CGFloat spacing = SPACING * maxBackingScaleFactor;

	// Helper to fill a rectangle with a solid color
	void (^fillRect)(CGRect rect, float r, float g, float b, float a) = ^(CGRect rect, float r, float g, float b, float a) {
		size_t startX = (size_t)rect.origin.x;
		size_t startY = (size_t)rect.origin.y;
		size_t endX = (size_t)(rect.origin.x + rect.size.width);
		size_t endY = (size_t)(rect.origin.y + rect.size.height);
		if (endX > width) endX = width;
		if (endY > height) endY = height;

		uint16_t hr = floatToHalfFloat(r);
		uint16_t hg = floatToHalfFloat(g);
		uint16_t hb = floatToHalfFloat(b);
		uint16_t ha = floatToHalfFloat(a);

		for (size_t y = startY; y < endY; y++) {
			for (size_t x = startX; x < endX; x++) {
				size_t idx = (y * width + x) * 4;
				pixels[idx + 0] = hr;
				pixels[idx + 1] = hg;
				pixels[idx + 2] = hb;
				pixels[idx + 3] = ha;
			}
		}
	};

	// Helper to stroke a rectangle outline
	void (^strokeRect)(CGRect rect, float r, float g, float b, size_t lineWidth) = ^(CGRect rect, float r, float g, float b, size_t lineWidth) {
		uint16_t hr = floatToHalfFloat(r);
		uint16_t hg = floatToHalfFloat(g);
		uint16_t hb = floatToHalfFloat(b);
		uint16_t ha = floatToHalfFloat(1.0f);

		size_t startX = (size_t)rect.origin.x;
		size_t startY = (size_t)rect.origin.y;
		size_t endX = (size_t)(rect.origin.x + rect.size.width);
		size_t endY = (size_t)(rect.origin.y + rect.size.height);

		// Top and bottom edges
		for (size_t x = startX; x < endX && x < width; x++) {
			for (size_t lw = 0; lw < lineWidth; lw++) {
				if (startY + lw < height) {
					size_t idx = ((startY + lw) * width + x) * 4;
					pixels[idx + 0] = hr; pixels[idx + 1] = hg; pixels[idx + 2] = hb; pixels[idx + 3] = ha;
				}
				if (endY > lw && endY - lw - 1 < height) {
					size_t idx = ((endY - lw - 1) * width + x) * 4;
					pixels[idx + 0] = hr; pixels[idx + 1] = hg; pixels[idx + 2] = hb; pixels[idx + 3] = ha;
				}
			}
		}

		// Left and right edges
		for (size_t y = startY; y < endY && y < height; y++) {
			for (size_t lw = 0; lw < lineWidth; lw++) {
				if (startX + lw < width) {
					size_t idx = (y * width + startX + lw) * 4;
					pixels[idx + 0] = hr; pixels[idx + 1] = hg; pixels[idx + 2] = hb; pixels[idx + 3] = ha;
				}
				if (endX > lw && endX - lw - 1 < width) {
					size_t idx = (y * width + endX - lw - 1) * 4;
					pixels[idx + 0] = hr; pixels[idx + 1] = hg; pixels[idx + 2] = hb; pixels[idx + 3] = ha;
				}
			}
		}
	};

	// Helper to set pixel with lighten blend mode
	void (^setPixelWithLighten)(size_t x, size_t y, float r, float g, float b) = ^(size_t x, size_t y, float r, float g, float b) {
		if (x >= width || y >= height) return;
		size_t idx = (y * width + x) * 4;
		float existingR = halfFloatToFloat(pixels[idx + 0]);
		float existingG = halfFloatToFloat(pixels[idx + 1]);
		float existingB = halfFloatToFloat(pixels[idx + 2]);
		// Lighten blend mode: take maximum
		pixels[idx + 0] = floatToHalfFloat(fmax(existingR, r));
		pixels[idx + 1] = floatToHalfFloat(fmax(existingG, g));
		pixels[idx + 2] = floatToHalfFloat(fmax(existingB, b));
	};

	if (graph == GSHoriStep || graph == GSHoriLinear) {
		// Horizontal graph - plot a ROW of pixels
		// Float16 buffer has top-left origin, Y increases downward
		CGRect graphRect = CGRectMake(spacing, spacing, width - 2*spacing, height/3);

		// Draw black canvas with white border
		fillRect(graphRect, 0.0f, 0.0f, 0.0f, 1.0f);
		strokeRect(graphRect, 1.0f, 1.0f, 1.0f, 2);

		// Plot RGB values from the center row of the red square.
		// zoomPixelCenterY is CGContext Y (lower-left), so the center pixel-buffer
		// row of the indicator/red square band is: height - zoomPixelCenterY - gridPixelSize/2
		size_t plotY = height - zoomPixelCenterY - gridPixelSize / 2;
		if (plotY >= height) return;

		// First pass: scan to find maximum value in the row
		float maxValue = 0.0f;
		for (size_t x = (size_t)graphRect.origin.x; x < graphRect.origin.x + graphRect.size.width && x < width; x++) {
			size_t srcIdx = (plotY * width + x) * 4;
			float r = halfFloatToFloat(pixels[srcIdx + 0]);
			float g = halfFloatToFloat(pixels[srcIdx + 1]);
			float b = halfFloatToFloat(pixels[srcIdx + 2]);
			if (r > maxValue) maxValue = r;
			if (g > maxValue) maxValue = g;
			if (b > maxValue) maxValue = b;
		}

		// Choose scaling based on max value
		// If all values <= 1.0, use 1.0 scale; otherwise use 4.0 scale
		float scaleMax = (maxValue <= 1.0f) ? 1.0f : 4.0f;
		self->useHDRScaleForGraphs = (scaleMax > 1.0f);

		// Draw reference lines at 1.0, 2.0, 3.0 if using 4.0 scale
		if (scaleMax > 1.0f) {
			// Faint light gray color for reference lines
			uint16_t refGray = floatToHalfFloat(0.2f);
			uint16_t refAlpha = floatToHalfFloat(1.0f);

			for (float refValue = 1.0f; refValue < 4.0f; refValue += 1.0f) {
				size_t refY = (size_t)(graphRect.origin.y + graphRect.size.height - (graphRect.size.height * (refValue / scaleMax)));
				if (refY >= graphRect.origin.y && refY < graphRect.origin.y + graphRect.size.height) {
					for (size_t x = (size_t)graphRect.origin.x; x < graphRect.origin.x + graphRect.size.width && x < width; x++) {
						size_t idx = (refY * width + x) * 4;
						pixels[idx + 0] = refGray;
						pixels[idx + 1] = refGray;
						pixels[idx + 2] = refGray;
						pixels[idx + 3] = refAlpha;
					}
				}
			}
		}

		size_t lineHeight = (size_t)maxBackingScaleFactor;
		if (lineHeight < 1) lineHeight = 1;

		// Track previous Y positions for vertical connectors
		size_t prevRY = 0, prevGY = 0, prevBY = 0;
		BOOL firstPixel = YES;

		for (size_t x = (size_t)graphRect.origin.x; x < graphRect.origin.x + graphRect.size.width && x < width; x++) {
			// Get pixel at this X position in the center row
			size_t srcIdx = (plotY * width + x) * 4;
			float r = halfFloatToFloat(pixels[srcIdx + 0]);
			float g = halfFloatToFloat(pixels[srcIdx + 1]);
			float b = halfFloatToFloat(pixels[srcIdx + 2]);

			// Map HDR values to graph height using adaptive scaling
			// Higher values go higher in the graph (smaller Y since origin is top-left)
			size_t rY = (size_t)(graphRect.origin.y + graphRect.size.height - (graphRect.size.height * fmin(r / scaleMax, 1.0f)));
			size_t gY = (size_t)(graphRect.origin.y + graphRect.size.height - (graphRect.size.height * fmin(g / scaleMax, 1.0f)));
			size_t bY = (size_t)(graphRect.origin.y + graphRect.size.height - (graphRect.size.height * fmin(b / scaleMax, 1.0f)));
			// Clamp so segment stays just inside top border at max value
			size_t hMinY = (size_t)graphRect.origin.y + lineHeight;
			if (rY < hMinY) rY = hMinY;
			if (gY < hMinY) gY = hMinY;
			if (bY < hMinY) bY = hMinY;

			// Draw vertical connectors from previous position (only for linear mode)
			if (graph == GSHoriLinear && !firstPixel) {
				// Draw vertical line from prevY to currentY for each channel
				size_t rStart = (rY < prevRY) ? rY : prevRY;
				size_t rEnd = (rY < prevRY) ? prevRY : rY;
				for (size_t y = rStart; y <= rEnd && y < height; y++) {
					for (size_t lh = 0; lh < lineHeight; lh++) {
						setPixelWithLighten(x, y + lh, 1.0f, 0.0f, 0.0f);
					}
				}

				size_t gStart = (gY < prevGY) ? gY : prevGY;
				size_t gEnd = (gY < prevGY) ? prevGY : gY;
				for (size_t y = gStart; y <= gEnd && y < height; y++) {
					for (size_t lh = 0; lh < lineHeight; lh++) {
						setPixelWithLighten(x, y + lh, 0.0f, 1.0f, 0.0f);
					}
				}

				size_t bStart = (bY < prevBY) ? bY : prevBY;
				size_t bEnd = (bY < prevBY) ? prevBY : bY;
				for (size_t y = bStart; y <= bEnd && y < height; y++) {
					for (size_t lh = 0; lh < lineHeight; lh++) {
						setPixelWithLighten(x, y + lh, 0.0f, 0.0f, 1.0f);
					}
				}
			}

			// Draw horizontal line segments at the calculated Y positions
			for (size_t lh = 0; lh < lineHeight && rY + lh < height; lh++) {
				setPixelWithLighten(x, rY + lh, 1.0f, 0.0f, 0.0f); // Red
			}
			for (size_t lh = 0; lh < lineHeight && gY + lh < height; lh++) {
				setPixelWithLighten(x, gY + lh, 0.0f, 1.0f, 0.0f); // Green
			}
			for (size_t lh = 0; lh < lineHeight && bY + lh < height; lh++) {
				setPixelWithLighten(x, bY + lh, 0.0f, 0.0f, 1.0f); // Blue
			}

			// Save current positions for next iteration
			prevRY = rY;
			prevGY = gY;
			prevBY = bY;
			firstPixel = NO;
		}
	} else if (graph == GSVertStep || graph == GSVertLinear) {
		// Vertical graph - plot a COLUMN of pixels
		// Float16 buffer has top-left origin
		CGRect graphRect = CGRectMake(width - spacing - width/3, spacing, width/3, height - 2*spacing);

		// Draw black canvas with white border
		fillRect(graphRect, 0.0f, 0.0f, 0.0f, 1.0f);
		strokeRect(graphRect, 1.0f, 1.0f, 1.0f, 2);

		// Plot RGB values from the center column inside the canvas
		// Read from the center of the red square (halfway through the magnified pixel)
		size_t plotX = zoomPixelCenterX + gridPixelSize / 2;
		if (plotX >= width) return;

		// First pass: scan to find maximum value in the column
		float maxValue = 0.0f;
		for (size_t y = (size_t)graphRect.origin.y; y < graphRect.origin.y + graphRect.size.height && y < height; y++) {
			size_t srcIdx = (y * width + plotX) * 4;
			float r = halfFloatToFloat(pixels[srcIdx + 0]);
			float g = halfFloatToFloat(pixels[srcIdx + 1]);
			float b = halfFloatToFloat(pixels[srcIdx + 2]);
			if (r > maxValue) maxValue = r;
			if (g > maxValue) maxValue = g;
			if (b > maxValue) maxValue = b;
		}

		// Choose scaling based on max value
		// If all values <= 1.0, use 1.0 scale; otherwise use 4.0 scale
		float scaleMax = (maxValue <= 1.0f) ? 1.0f : 4.0f;
		self->useHDRScaleForGraphs = (scaleMax > 1.0f);

		// Draw vertical reference lines at 1.0, 2.0, 3.0 if using 4.0 scale
		if (scaleMax > 1.0f) {
			// Faint light gray color for reference lines
			uint16_t refGray = floatToHalfFloat(0.2f);
			uint16_t refAlpha = floatToHalfFloat(1.0f);

			for (float refValue = 1.0f; refValue < 4.0f; refValue += 1.0f) {
				size_t refX = (size_t)(graphRect.origin.x + (graphRect.size.width * (refValue / scaleMax)));
				if (refX >= graphRect.origin.x && refX < graphRect.origin.x + graphRect.size.width) {
					for (size_t y = (size_t)graphRect.origin.y; y < graphRect.origin.y + graphRect.size.height && y < height; y++) {
						size_t idx = (y * width + refX) * 4;
						pixels[idx + 0] = refGray;
						pixels[idx + 1] = refGray;
						pixels[idx + 2] = refGray;
						pixels[idx + 3] = refAlpha;
					}
				}
			}
		}

		size_t lineWidth = (size_t)maxBackingScaleFactor;
		if (lineWidth < 1) lineWidth = 1;

		// Track previous X positions for horizontal connectors
		size_t prevRX = 0, prevGX = 0, prevBX = 0;
		BOOL firstPixel = YES;

		for (size_t y = (size_t)graphRect.origin.y; y < graphRect.origin.y + graphRect.size.height && y < height; y++) {
			// Get pixel at this Y position in the center column
			size_t srcIdx = (y * width + plotX) * 4;
			float r = halfFloatToFloat(pixels[srcIdx + 0]);
			float g = halfFloatToFloat(pixels[srcIdx + 1]);
			float b = halfFloatToFloat(pixels[srcIdx + 2]);

			// Map HDR values to graph width using adaptive scaling
			// Higher values extend further to the right
			size_t rX = (size_t)(graphRect.origin.x + (graphRect.size.width * fmin(r / scaleMax, 1.0f)));
			size_t gX = (size_t)(graphRect.origin.x + (graphRect.size.width * fmin(g / scaleMax, 1.0f)));
			size_t bX = (size_t)(graphRect.origin.x + (graphRect.size.width * fmin(b / scaleMax, 1.0f)));
			// Clamp so segment stays just inside right border at max value
			size_t vMaxX = (size_t)(graphRect.origin.x + graphRect.size.width) - 2*lineWidth;
			if (rX > vMaxX) rX = vMaxX;
			if (gX > vMaxX) gX = vMaxX;
			if (bX > vMaxX) bX = vMaxX;

			// Draw horizontal connectors from previous position (only for linear mode)
			if (graph == GSVertLinear && !firstPixel) {
				// Draw horizontal line from prevX to currentX for each channel
				size_t rStart = (rX < prevRX) ? rX : prevRX;
				size_t rEnd = (rX < prevRX) ? prevRX : rX;
				for (size_t x = rStart; x <= rEnd && x < width; x++) {
					for (size_t lw = 0; lw < lineWidth; lw++) {
						setPixelWithLighten(x + lw, y, 1.0f, 0.0f, 0.0f);
					}
				}

				size_t gStart = (gX < prevGX) ? gX : prevGX;
				size_t gEnd = (gX < prevGX) ? prevGX : gX;
				for (size_t x = gStart; x <= gEnd && x < width; x++) {
					for (size_t lw = 0; lw < lineWidth; lw++) {
						setPixelWithLighten(x + lw, y, 0.0f, 1.0f, 0.0f);
					}
				}

				size_t bStart = (bX < prevBX) ? bX : prevBX;
				size_t bEnd = (bX < prevBX) ? prevBX : bX;
				for (size_t x = bStart; x <= bEnd && x < width; x++) {
					for (size_t lw = 0; lw < lineWidth; lw++) {
						setPixelWithLighten(x + lw, y, 0.0f, 0.0f, 1.0f);
					}
				}
			}

			// Draw vertical line segments at the calculated X positions
			for (size_t lw = 0; lw < lineWidth && rX + lw < width; lw++) {
				setPixelWithLighten(rX + lw, y, 1.0f, 0.0f, 0.0f); // Red
			}
			for (size_t lw = 0; lw < lineWidth && gX + lw < width; lw++) {
				setPixelWithLighten(gX + lw, y, 0.0f, 1.0f, 0.0f); // Green
			}
			for (size_t lw = 0; lw < lineWidth && bX + lw < width; lw++) {
				setPixelWithLighten(bX + lw, y, 0.0f, 0.0f, 1.0f); // Blue
			}

			// Save current positions for next iteration
			prevRX = rX;
			prevGX = gX;
			prevBX = bX;
			firstPixel = NO;
		}
	}

}

// Create a CGImage with HDR color space from a region of the pixel buffer
- (CGImageRef) createHDRCGImageFromBuffer:(CVPixelBufferRef)pixelBuffer
									rectX:(size_t)rectX
									rectY:(size_t)rectY
									width:(size_t)width
								   height:(size_t)height
									 zoom:(size_t)zoomFactor API_AVAILABLE(macos(12.3)) {

	// Validate dimensions
	if (width == 0 || height == 0 || zoomFactor == 0) {
		return nil;
	}

	// Save actual captured dimensions for red square positioning
	actualCapturedWidth = width;
	actualCapturedHeight = height;

	OSType pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer);
	
	// For now, only handle Float16 RGBA format
	// YCbCr would require conversion which we'll skip for the HDR path
	if (pixelFormat != kCVPixelFormatType_64RGBAHalf) {
		NSLog(@"⚠️ HDR magnified view requires Float16 RGBA format, got: %d", pixelFormat);
		return nil;
	}
	
	CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
	
	void *baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer);
	size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
	size_t bufferWidth = CVPixelBufferGetWidth(pixelBuffer);
	size_t bufferHeight = CVPixelBufferGetHeight(pixelBuffer);
	
	// Calculate zoomed size
	size_t zoomedWidth = width * zoomFactor;
	size_t zoomedHeight = height * zoomFactor;
	
	// Crop the zoom buffer symmetrically to viewPixelWidth/Height so Metal renders 1:1
	// (matches the SDR path which uses CGImageCreateWithImageInRect with cropPixelY/2 offset).
	// This eliminates the HDR vs SDR scale mismatch and the apparent content shift.
	size_t cropOffsetX = (zoomedWidth > viewPixelWidth) ? (zoomedWidth - viewPixelWidth) / 2 : 0;
	size_t cropOffsetY = (zoomedHeight > viewPixelHeight) ? (zoomedHeight - viewPixelHeight) / 2 : 0;
	size_t outputWidth  = (zoomedWidth  > viewPixelWidth)  ? viewPixelWidth  : zoomedWidth;
	size_t outputHeight = (zoomedHeight > viewPixelHeight) ? viewPixelHeight : zoomedHeight;
	
	// Tag CGImage with Extended Display P3 (gamma-encoded)
	// The actual display interpretation is determined by CAMetalLayer.colorspace in HDRImageView
	CGColorSpaceRef hdrColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedDisplayP3);
	if (!hdrColorSpace) {
		hdrColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
	}
	
	// Allocate Float16 RGBA buffer for zoomed image
	size_t zoomedBytesPerRow = outputWidth * 8; // 4 channels × 2 bytes (Float16)
	void *zoomedData = malloc(outputHeight * zoomedBytesPerRow);
	if (!zoomedData) {
		CGColorSpaceRelease(hdrColorSpace);
		CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
		return nil;
	}
	
	// Perform nearest-neighbor zoom (preserves exact pixel values for inspection)
	uint16_t *zoomedPixels = (uint16_t *)zoomedData;
	
	for (size_t y = 0; y < outputHeight; y++) {
		size_t srcY = rectY + ((y + cropOffsetY) / zoomFactor);
		if (srcY >= bufferHeight) srcY = bufferHeight - 1;
		
		uint16_t *srcRow = (uint16_t *)((char *)baseAddress + srcY * bytesPerRow);
		
		for (size_t x = 0; x < outputWidth; x++) {
			size_t srcX = rectX + ((x + cropOffsetX) / zoomFactor);
			if (srcX >= bufferWidth) srcX = bufferWidth - 1;
			
			uint16_t *srcPixel = &srcRow[srcX * 4];
			uint16_t *dstPixel = &zoomedPixels[(y * outputWidth + x) * 4];
			
			// Copy pixel data directly (nearest-neighbor zoom)
			dstPixel[0] = srcPixel[0]; // R
			dstPixel[1] = srcPixel[1]; // G
			dstPixel[2] = srcPixel[2]; // B
			dstPixel[3] = srcPixel[3]; // A
		}
	}

	CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);

	// Calculate red square position (needed for both graphs and red square drawing)
	// This needs to be done AFTER unlocking the pixel buffer

	// Calculate positions
	size_t centerZoomedX, centerZoomedY;
	size_t squareSize;
	{
		// The square represents one source pixel after zooming in display coordinates
		// After zoom: 1 source pixel becomes (zoomFactor × maxBackingScaleFactor / 2) display pixels
		squareSize = (size_t)(zoomFactor * maxBackingScaleFactor / 2.0);
		if (squareSize < 1) squareSize = 1;

		// Calculate X position using output (cropped) width center
		size_t geometricX = (outputWidth / 2) - (squareSize / 2);
		centerZoomedX = geometricX + (squareSize / 2);


		// Y-DOWN row in the output buffer where the cursor pixel block starts.
		// capturePixelCenterY * zoomFactor is the row in the full zoom buffer;
		// subtract cropOffsetY to get the row in the cropped output buffer.
		centerZoomedY = capturePixelCenterY * zoomFactor - cropOffsetY;

		// Bounds check
		if (centerZoomedX + squareSize > outputWidth) {
			centerZoomedX = outputWidth > squareSize ? outputWidth - squareSize : 0;
		}
		if (centerZoomedY + squareSize > outputHeight) {
			centerZoomedY = outputHeight > squareSize ? outputHeight - squareSize : 0;
		}


		// Store positions for graph indicator lines.
		// zoomPixelCenterY is a CGContext Y (lower-left origin) in the output buffer.
		self->zoomPixelCenterX = centerZoomedX;
		self->zoomPixelCenterY = outputHeight - centerZoomedY - squareSize;
		self->gridPixelSize = squareSize;

	}

	// Draw graphs directly into Float16 buffer BEFORE drawing red square
	// This ensures plots read desktop content, not the red square itself
	if (graph != GSNone) {
		[self drawGraphsIntoFloat16Buffer:zoomedPixels
									 width:outputWidth
									height:outputHeight
							  bytesPerRow:zoomedBytesPerRow
								      zoom:zoomFactor];
	}

	// Now draw the red square on top
	{
		// Red color in Float16 - use a VERY bright red to ensure visibility
		uint16_t redR = floatToHalfFloat(10.0f);  // Super bright HDR red!
		uint16_t redG = floatToHalfFloat(0.0f);
		uint16_t redB = floatToHalfFloat(0.0f);
		uint16_t redA = floatToHalfFloat(1.0f);

		// Draw the square outline (stroke)
		size_t lineWidth = (size_t)maxBackingScaleFactor;
		if (lineWidth < 1) lineWidth = 1;
		
		// Top edge
		for (size_t x = centerZoomedX; x < centerZoomedX + squareSize && x < outputWidth; x++) {
			for (size_t lw = 0; lw < lineWidth && centerZoomedY + lw < outputHeight; lw++) {
				size_t idx = ((centerZoomedY + lw) * outputWidth + x) * 4;
				zoomedPixels[idx + 0] = redR;
				zoomedPixels[idx + 1] = redG;
				zoomedPixels[idx + 2] = redB;
				zoomedPixels[idx + 3] = redA;
			}
		}
		
		// Bottom edge
		size_t bottomY = centerZoomedY + squareSize - lineWidth;
		if (bottomY < outputHeight) {
			for (size_t x = centerZoomedX; x < centerZoomedX + squareSize && x < outputWidth; x++) {
				for (size_t lw = 0; lw < lineWidth && bottomY + lw < outputHeight; lw++) {
					size_t idx = ((bottomY + lw) * outputWidth + x) * 4;
					zoomedPixels[idx + 0] = redR;
					zoomedPixels[idx + 1] = redG;
					zoomedPixels[idx + 2] = redB;
					zoomedPixels[idx + 3] = redA;
				}
			}
		}
		
		// Left edge
		for (size_t y = centerZoomedY; y < centerZoomedY + squareSize && y < outputHeight; y++) {
			for (size_t lw = 0; lw < lineWidth && centerZoomedX + lw < outputWidth; lw++) {
				size_t idx = (y * outputWidth + centerZoomedX + lw) * 4;
				zoomedPixels[idx + 0] = redR;
				zoomedPixels[idx + 1] = redG;
				zoomedPixels[idx + 2] = redB;
				zoomedPixels[idx + 3] = redA;
			}
		}
		
		// Right edge
		size_t rightX = centerZoomedX + squareSize - lineWidth;
		if (rightX < outputWidth) {
			for (size_t y = centerZoomedY; y < centerZoomedY + squareSize && y < outputHeight; y++) {
				for (size_t lw = 0; lw < lineWidth && rightX + lw < outputWidth; lw++) {
					size_t idx = (y * outputWidth + rightX + lw) * 4;
					zoomedPixels[idx + 0] = redR;
					zoomedPixels[idx + 1] = redG;
					zoomedPixels[idx + 2] = redB;
					zoomedPixels[idx + 3] = redA;
				}
			}
		}
		
	}

	// Create CGImage from Float16 data with HDR color space
	// The data provider will take ownership and free zoomedData when done
	CGDataProviderRef provider = CGDataProviderCreateWithData(
		NULL, zoomedData, outputHeight * zoomedBytesPerRow,
		releaseDataCallback  // Will free zoomedData when CGImage is released
	);
	
	if (!provider) {
		free(zoomedData);
		CGColorSpaceRelease(hdrColorSpace);
		return nil;
	}
	
	CGImageRef image = CGImageCreate(
		outputWidth, outputHeight,
		16, // bits per component (Float16)
		64, // bits per pixel (4 × 16)
		zoomedBytesPerRow,
		hdrColorSpace,  // NULL to avoid color management
		kCGImageAlphaPremultipliedLast | kCGBitmapFloatComponents | kCGBitmapByteOrder16Little,
		provider,
		NULL, // decode
		false, // shouldInterpolate (we want nearest neighbor for pixel inspection)
		kCGRenderingIntentDefault
	);
	
	CGDataProviderRelease(provider);
	CGColorSpaceRelease(hdrColorSpace);
	
	return image; // Caller must release
}

// Convert YCbCr to RGB with HDR preservation
- (BOOL) extractRegionFromYCbCrBuffer:(CVPixelBufferRef)pixelBuffer
                               toData:(unsigned char*)data
                                width:(size_t)dataWidth
                               height:(size_t)dataHeight
                         bytesPerRow:(size_t)dataBytesPerRow
                               rectX:(size_t)rectX
                               rectY:(size_t)rectY API_AVAILABLE(macos(12.3)) {
	
	CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
	
	size_t bufferWidth = CVPixelBufferGetWidth(pixelBuffer);
	size_t bufferHeight = CVPixelBufferGetHeight(pixelBuffer);
	OSType pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer);
	
	// Bounds check
	if (rectX + dataWidth > bufferWidth || rectY + dataHeight > bufferHeight) {
		NSLog(@"❌ YCbCr region out of bounds: rect(%zu,%zu,%zu,%zu) buffer(%zu,%zu)",
			rectX, rectY, dataWidth, dataHeight, bufferWidth, bufferHeight);
		CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
		return NO;
	}
	
	// Get Y and CbCr plane data
	void *yPlane = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
	void *cbcrPlane = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
	size_t yBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
	size_t cbcrBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
	
	BOOL is10Bit = (pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange ||
	                pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarFullRange);
	BOOL isFullRange = (pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange ||
	                    pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarFullRange);
	
	// Diagnostic: Check what format we actually got
	static dispatch_once_t formatOnce;
	dispatch_once(&formatOnce, ^{
		NSLog(@"📺 Requested format vs actual:");
		NSLog(@"   Expected: kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange (0x%X)", 
			  kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange);
		NSLog(@"   Actually got: 0x%X ('%c%c%c%c')",
			  pixelFormat,
			  (pixelFormat >> 24) & 0xFF,
			  (pixelFormat >> 16) & 0xFF,
			  (pixelFormat >> 8) & 0xFF,
			  pixelFormat & 0xFF);
	});
	
#ifdef SNOOPX_VERBOSE
	const char *formatName = "Unknown";
	if (pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) formatName = "8-bit YCbCr Video Range";
	else if (pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) formatName = "8-bit YCbCr Full Range";
	else if (pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange) formatName = "10-bit YCbCr Video Range";
	else if (pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarFullRange) formatName = "10-bit YCbCr Full Range";
	
	
	if (!is10Bit) {
		NSLog(@"   ⚠️  LIMITATION: 8-bit YCbCr can only represent RGB values up to ~1.09!");
		NSLog(@"   ⚠️  For HDR values > 1.1, Apple needs to provide 10-bit YCbCr format.");
		NSLog(@"   ⚠️  This is a ScreenCaptureKit preset limitation, not a bug in this code.");
	}
#endif
	
	// YCbCr to RGB conversion matrix (ITU-R BT.709 for HDR content)
	CGFloat maxValue = 0.0;
	
	for (size_t y = 0; y < dataHeight; y++) {
		unsigned char *dstRow = data + y * dataBytesPerRow;
		
		for (size_t x = 0; x < dataWidth; x++) {
			unsigned char *dstPixel = &dstRow[x * bytesPerPixel];
			
			size_t srcY = rectY + y;
			size_t srcX = rectX + x;
			
			CGFloat yVal, cbVal, crVal;
			
			if (is10Bit) {
				// 10-bit YCbCr (supports HDR)
				// 10-bit values are stored as little-endian uint16_t, using lower 10 bits
				uint16_t *yPtr = (uint16_t *)((char *)yPlane + srcY * yBytesPerRow);
				uint16_t *cbcrPtr = (uint16_t *)((char *)cbcrPlane + (srcY / 2) * cbcrBytesPerRow);
				
				// Diagnostic: Print raw values for first pixel to understand bit packing
				static dispatch_once_t onceToken;
				dispatch_once(&onceToken, ^{
					uint16_t yRawDirect = yPtr[srcX];
					uint16_t cbRawDirect = cbcrPtr[(srcX / 2) * 2];
					uint16_t crRawDirect = cbcrPtr[(srcX / 2) * 2 + 1];
					
					NSLog(@"   Y raw:  0x%04X = %u", yRawDirect, yRawDirect);
					NSLog(@"   Cb raw: 0x%04X = %u", cbRawDirect, cbRawDirect);
					NSLog(@"   Cr raw: 0x%04X = %u", crRawDirect, crRawDirect);
					NSLog(@"   Shifted right 6 (CORRECT): Y=%u, Cb=%u, Cr=%u", 
						  yRawDirect >> 6, cbRawDirect >> 6, crRawDirect >> 6);
					NSLog(@"   Lower 10 bits (WRONG): Y=%u, Cb=%u, Cr=%u",
						  yRawDirect & 0x3FF, cbRawDirect & 0x3FF, crRawDirect & 0x3FF);
				});
				
				// FIXED: Read from UPPER 10 bits (shift right 6)
				// Apple stores 10-bit video in bits [15:6] of uint16_t
				uint16_t yRaw = yPtr[srcX] >> 6;
				uint16_t cbRaw = cbcrPtr[(srcX / 2) * 2] >> 6;
				uint16_t crRaw = cbcrPtr[(srcX / 2) * 2 + 1] >> 6;
				
				yVal = yRaw / 1023.0;
				cbVal = cbRaw / 1023.0;
				crVal = crRaw / 1023.0;
			} else {
				// 8-bit YCbCr
				uint8_t *yPtr = (uint8_t *)yPlane + srcY * yBytesPerRow;
				uint8_t *cbcrPtr = (uint8_t *)cbcrPlane + (srcY / 2) * cbcrBytesPerRow;
				
				yVal = yPtr[srcX] / 255.0;
				cbVal = cbcrPtr[(srcX / 2) * 2] / 255.0;
				crVal = cbcrPtr[(srcX / 2) * 2 + 1] / 255.0;
			}
			
			// Adjust for video range if needed
			if (!isFullRange) {
				// Video range: Y=[16,235], CbCr=[16,240] mapped to [0,1]
				yVal = (yVal - 16.0/255.0) * (255.0/219.0);
				cbVal = (cbVal - 16.0/255.0) * (255.0/224.0);
				crVal = (crVal - 16.0/255.0) * (255.0/224.0);
			}
			
			// Convert to RGB (BT.709) - DON'T CLAMP during conversion!
			CGFloat r = yVal + 1.5748 * (crVal - 0.5);
			CGFloat g = yVal - 0.1873 * (cbVal - 0.5) - 0.4681 * (crVal - 0.5);
			CGFloat b = yVal + 1.8556 * (cbVal - 0.5);
			
			// Track max for debugging
			if (r > maxValue) maxValue = r;
			if (g > maxValue) maxValue = g;
			if (b > maxValue) maxValue = b;
			
			// For 8-bit display: clamp ONLY for display, not for cursor readout
			// Cursor reading happens directly from YCbCr buffer in readHDRPixelFromBuffer
			CGFloat displayR = fmax(0.0, fmin(1.0, r));
			CGFloat displayG = fmax(0.0, fmin(1.0, g));
			CGFloat displayB = fmax(0.0, fmin(1.0, b));
			
			// Convert to 8-bit BGRA
			dstPixel[BLUE_INDEX] = (unsigned char)(displayB * 255.0);
			dstPixel[GREEN_INDEX] = (unsigned char)(displayG * 255.0);
			dstPixel[RED_INDEX] = (unsigned char)(displayR * 255.0);
			dstPixel[ALPHA_INDEX] = 255;
		}
	}
	
#ifdef SNOOPX_VERBOSE
	NSLog(@"   Max RGB component in this capture: %.4f", maxValue);
	if (maxValue > 1.0) {
		NSLog(@"   ✅ HDR VALUES PRESERVED! (max = %.4f)", maxValue);
	} else if (maxValue > 0.99) {
		NSLog(@"   📊 Near-SDR peak (%.4f) - may be HDR content in 8-bit YCbCr", maxValue);
		NSLog(@"   💡 8-bit YCbCr video range max is ~1.09, so values >0.94 suggest HDR source");
	} else {
		NSLog(@"   ⚠️  No HDR detected (max = %.4f)", maxValue);
	}
#endif
	
	CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
	return YES;
}

// Compute HDR statistics from Float16 pixel buffer
- (void) computeHDRStatisticsFromBuffer:(CVPixelBufferRef)pixelBuffer
								  rectX:(size_t)rectX
								  rectY:(size_t)rectY
								  width:(size_t)width
								 height:(size_t)height API_AVAILABLE(macos(12.3)) {
	if (!pixelBuffer) {
		NSLog(@"⚠️ computeHDRStatistics: null buffer");
		return;
	}

	OSType pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer);
	if (pixelFormat != kCVPixelFormatType_64RGBAHalf) {
		NSLog(@"⚠️ computeHDRStatistics: not Float16 format");
		return;
	}

	CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);

	size_t bufferWidth = CVPixelBufferGetWidth(pixelBuffer);
	size_t bufferHeight = CVPixelBufferGetHeight(pixelBuffer);
	size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
	uint16_t *baseAddress = (uint16_t *)CVPixelBufferGetBaseAddress(pixelBuffer);

	if (!baseAddress) {
		CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
		NSLog(@"⚠️ computeHDRStatistics: null base address");
		return;
	}

	// Bounds check
	if (rectX + width > bufferWidth || rectY + height > bufferHeight) {
		CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
		NSLog(@"⚠️ computeHDRStatistics: region out of bounds");
		return;
	}

	// Initialize statistics
	hdrMinR = hdrMinG = hdrMinB = CGFLOAT_MAX;
	hdrMaxR = hdrMaxG = hdrMaxB = 0.0;
	hdrAvgR = hdrAvgG = hdrAvgB = 0.0;

	// Collect all values for median calculation
	size_t totalPixels = width * height;
	CGFloat *valuesR = malloc(totalPixels * sizeof(CGFloat));
	CGFloat *valuesG = malloc(totalPixels * sizeof(CGFloat));
	CGFloat *valuesB = malloc(totalPixels * sizeof(CGFloat));
	int pixelIndex = 0;

	// Compute min, max, sum (for avg), and collect values (for median)
	for (size_t y = 0; y < height; y++) {
		size_t bufferY = rectY + y;
		uint16_t *row = (uint16_t *)((uint8_t *)baseAddress + bufferY * bytesPerRow);

		for (size_t x = 0; x < width; x++) {
			size_t bufferX = rectX + x;
			uint16_t *pixel = &row[bufferX * 4]; // RGBA, 4 components

			// Convert Float16 to float and clamp to prevent negatives
			CGFloat r = fmax(0.0, halfFloatToFloat(pixel[0]));
			CGFloat g = fmax(0.0, halfFloatToFloat(pixel[1]));
			CGFloat b = fmax(0.0, halfFloatToFloat(pixel[2]));

			// Update min/max
			if (r < hdrMinR) hdrMinR = r;
			if (r > hdrMaxR) hdrMaxR = r;
			if (g < hdrMinG) hdrMinG = g;
			if (g > hdrMaxG) hdrMaxG = g;
			if (b < hdrMinB) hdrMinB = b;
			if (b > hdrMaxB) hdrMaxB = b;

			// Accumulate for average
			hdrAvgR += r;
			hdrAvgG += g;
			hdrAvgB += b;

			// Store for median
			valuesR[pixelIndex] = r;
			valuesG[pixelIndex] = g;
			valuesB[pixelIndex] = b;
			pixelIndex++;
		}
	}

	// Compute averages
	hdrAvgR /= totalPixels;
	hdrAvgG /= totalPixels;
	hdrAvgB /= totalPixels;

	// Compute medians (simple sort and pick middle)
	qsort(valuesR, totalPixels, sizeof(CGFloat), compare_cgfloat);
	qsort(valuesG, totalPixels, sizeof(CGFloat), compare_cgfloat);
	qsort(valuesB, totalPixels, sizeof(CGFloat), compare_cgfloat);

	hdrMedR = valuesR[totalPixels / 2];
	hdrMedG = valuesG[totalPixels / 2];
	hdrMedB = valuesB[totalPixels / 2];

	// Final clamp to ensure all values are non-negative
	hdrMinR = fmax(0.0, hdrMinR);
	hdrMinG = fmax(0.0, hdrMinG);
	hdrMinB = fmax(0.0, hdrMinB);
	hdrMaxR = fmax(0.0, hdrMaxR);
	hdrMaxG = fmax(0.0, hdrMaxG);
	hdrMaxB = fmax(0.0, hdrMaxB);
	hdrAvgR = fmax(0.0, hdrAvgR);
	hdrAvgG = fmax(0.0, hdrAvgG);
	hdrAvgB = fmax(0.0, hdrAvgB);
	hdrMedR = fmax(0.0, hdrMedR);
	hdrMedG = fmax(0.0, hdrMedG);
	hdrMedB = fmax(0.0, hdrMedB);

	free(valuesR);
	free(valuesG);
	free(valuesB);

	CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
}

// Extract and convert HDR pixel buffer region to 8-bit RGB for display
- (BOOL) extractRegionFromHDRBuffer:(CVPixelBufferRef)pixelBuffer 
                             toData:(unsigned char*)data
                              width:(size_t)dataWidth
                             height:(size_t)dataHeight
                       bytesPerRow:(size_t)dataBytesPerRow
                             rectX:(size_t)rectX
                             rectY:(size_t)rectY API_AVAILABLE(macos(12.3)) {
	if (!pixelBuffer || !data) {
		return NO;
	}
	
	OSType pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer);
	
	// Check if it's a YCbCr format - convert to RGB first
	if (pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange ||
		pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange ||
		pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange ||
		pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarFullRange) {
		
#ifdef SNOOPX_VERBOSE
		NSLog(@"📺 YCbCr format detected (%d), routing to YCbCr converter...", pixelFormat);
#endif
		return [self extractRegionFromYCbCrBuffer:pixelBuffer 
										  toData:data 
										   width:dataWidth 
										  height:dataHeight 
									 bytesPerRow:dataBytesPerRow 
										   rectX:rectX 
										   rectY:rectY];
	}
	
	CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
	
	void *baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer);
	size_t bufferBytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
	size_t bufferWidth = CVPixelBufferGetWidth(pixelBuffer);
	size_t bufferHeight = CVPixelBufferGetHeight(pixelBuffer);
	
	// Verify format for RGB formats
	if (pixelFormat != kCVPixelFormatType_64RGBAHalf && pixelFormat != kCVPixelFormatType_128RGBAFloat) {
		NSLog(@"❌ Unexpected pixel format: %d (expected RGB Half/Float or YCbCr)", pixelFormat);
		CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
		return NO;
	}
	
	BOOL isFloat32 = (pixelFormat == kCVPixelFormatType_128RGBAFloat);

	// Bounds check
	if (rectX + dataWidth > bufferWidth || rectY + dataHeight > bufferHeight) {
		NSLog(@"❌ Region out of bounds: rect(%zu,%zu,%zu,%zu) buffer(%zu,%zu)",
			rectX, rectY, dataWidth, dataHeight, bufferWidth, bufferHeight);
		CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
		return NO;
	}
	
#ifdef SNOOPX_VERBOSE
#endif
	
	// Convert HDR half-float to 8-bit RGB
	CGFloat maxR = 0, maxG = 0, maxB = 0;
	for (size_t y = 0; y < dataHeight; y++) {
		unsigned char *dstRow = data + y * dataBytesPerRow;
		
		for (size_t x = 0; x < dataWidth; x++) {
			unsigned char *dstPixel = &dstRow[x * bytesPerPixel];
			
			CGFloat r, g, b, a;
			
			if (isFloat32) {
				// Float32 format (16 bytes per pixel)
				float *srcRow = (float *)((char *)baseAddress + (rectY + y) * bufferBytesPerRow);
				float *srcPixel = &srcRow[(rectX + x) * 4];
				r = srcPixel[0];
				g = srcPixel[1];
				b = srcPixel[2];
				a = srcPixel[3];
			} else {
				// Half-float format (8 bytes per pixel)
				uint16_t *srcRow = (uint16_t *)((char *)baseAddress + (rectY + y) * bufferBytesPerRow);
				uint16_t *srcPixel = &srcRow[(rectX + x) * 4];
				r = halfFloatToFloat(srcPixel[0]);
				g = halfFloatToFloat(srcPixel[1]);
				b = halfFloatToFloat(srcPixel[2]);
				a = halfFloatToFloat(srcPixel[3]);
			}
			
			// Track maximum values for debugging
			if (r > maxR) maxR = r;
			if (g > maxG) maxG = g;
			if (b > maxB) maxB = b;
			
			// For 8-bit display: clamp to [0, 1] and convert to 8-bit
			// Note: The actual HDR values are preserved in latestPixelBuffer
			// and read directly in readHDRPixelFromBuffer for accurate color readout
			r = fmax(0.0, fmin(1.0, r));
			g = fmax(0.0, fmin(1.0, g));
			b = fmax(0.0, fmin(1.0, b));
			a = fmax(0.0, fmin(1.0, a));
			
			// Convert to 8-bit (BGRA format)
			dstPixel[BLUE_INDEX] = (unsigned char)(b * 255.0);
			dstPixel[GREEN_INDEX] = (unsigned char)(g * 255.0);
			dstPixel[RED_INDEX] = (unsigned char)(r * 255.0);
			dstPixel[ALPHA_INDEX] = (unsigned char)(a * 255.0);
		}
	}
	
#ifdef SNOOPX_VERBOSE
#endif
	
	CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
	return YES;
}

- (void) setupScreenCaptureKit API_AVAILABLE(macos(12.3)) {
	if (!isHDRCapable) {
#ifdef SNOOPX_VERBOSE
		NSLog(@"Display not HDR-capable, skipping ScreenCaptureKit setup");
#endif
		return;
	}

	// Set currentDisplayFrame synchronously from NSScreen before the async
	// getShareableContentWithCompletionHandler: call completes. This ensures
	// correct coordinate clamping on the very first updateView call.
	// The async completion handler will overwrite this with the confirmed value.
	if (currentDisplayFrame.size.width == 0) {
		NSScreen *screenToUse = nil;
		if (targetDisplayID != 0) {
			for (NSScreen *screen in [NSScreen screens]) {
				CGDirectDisplayID screenID = [[[screen deviceDescription] valueForKey:@"NSScreenNumber"] unsignedIntValue];
				if (screenID == targetDisplayID) {
					screenToUse = screen;
					break;
				}
			}
		}
		if (!screenToUse) screenToUse = [NSScreen mainScreen];
		if (screenToUse) currentDisplayFrame = [screenToUse frame];
	}

#ifdef SNOOPX_VERBOSE
	NSLog(@"Setting up ScreenCaptureKit for HDR capture...");
#endif

	[SCShareableContent getShareableContentWithCompletionHandler:^(SCShareableContent *content, NSError *error) {
		if (error) {
			NSLog(@"Error getting shareable content: %@", error);
			return;
		}
		
		if (content.displays.count == 0) {
			NSLog(@"❌ No displays available for capture");
			return;
		}
		
		// Find the display to capture
		SCDisplay *targetDisplay = nil;
		CGDirectDisplayID displayIDToCapture = 0;
		CGFloat maxArea = 0.0;

		// Use the target display if specified (e.g., from display switching)
		if (self->targetDisplayID != 0) {
			displayIDToCapture = self->targetDisplayID;
		} else {
#ifdef SNOOPX_VERBOSE
			NSLog(@"Searching for primary/largest HDR display...");
#endif
			// Try to find the main screen first (where menu bar is)
			NSScreen *mainNSScreen = [NSScreen mainScreen];
			if (mainNSScreen) {
				NSDictionary *description = [mainNSScreen deviceDescription];
				displayIDToCapture = [[description valueForKey:@"NSScreenNumber"] unsignedIntValue];
#ifdef SNOOPX_VERBOSE
				NSLog(@"  Using main screen (menu bar): ID=%u, frame=%@",
					displayIDToCapture, NSStringFromRect([mainNSScreen frame]));
#endif
			}
		}

		// If main screen isn't HDR, find the largest screen
		if (displayIDToCapture == 0) {
			for (NSScreen *screen in [NSScreen screens]) {
				NSDictionary *description = [screen deviceDescription];
				CGDirectDisplayID screenID = [[description valueForKey:@"NSScreenNumber"] unsignedIntValue];
				NSRect frame = [screen frame];
				CGFloat area = frame.size.width * frame.size.height;

				CGFloat edrValue = 1.0;
				if (@available(macOS 10.15, *)) {
					edrValue = screen.maximumPotentialExtendedDynamicRangeColorComponentValue;
				}
#ifdef SNOOPX_VERBOSE
				NSLog(@"  Screen ID %u: EDR=%.1f, area=%.0f, frame=%@",
					screenID, edrValue, area, NSStringFromRect(frame));
#endif
				if (area > maxArea) {
					maxArea = area;
					displayIDToCapture = screenID;
#ifdef SNOOPX_VERBOSE
					NSLog(@"    ^ New largest display!");
#endif
				}
			}
		}
#ifdef SNOOPX_VERBOSE
		NSLog(@"Selected display ID %u", displayIDToCapture);
#endif
		// If we didn't find any HDR display, use the first one
		if (displayIDToCapture == 0 && content.displays.count > 0) {
			displayIDToCapture = content.displays.firstObject.displayID;
#ifdef SNOOPX_VERBOSE
			NSLog(@"No HDR displays found, using first available: ID %u", displayIDToCapture);
#endif
		}

		// Find the matching SCDisplay
		for (SCDisplay *display in content.displays) {
			if (display.displayID == displayIDToCapture) {
				targetDisplay = display;
				break;
			}
		}
		
		if (!targetDisplay) {
			targetDisplay = content.displays.firstObject;
		}
		
		currentDisplay = [targetDisplay retain];
		self->targetDisplayID = targetDisplay.displayID; // Store for tracking

#ifdef SNOOPX_VERBOSE
		NSLog(@"Capturing from display: %@ (ID: %u, %zux%zu)",
			targetDisplay,
			targetDisplay.displayID,
			(size_t)targetDisplay.width,
			(size_t)targetDisplay.height);
#endif
		
		// DIAGNOSTIC 1: Verify display is in HDR mode
		NSScreen *diagnosticScreen = nil;
		for (NSScreen *screen in [NSScreen screens]) {
			NSDictionary *description = [screen deviceDescription];
			CGDirectDisplayID screenID = [[description valueForKey:@"NSScreenNumber"] unsignedIntValue];
			if (screenID == targetDisplay.displayID) {
				diagnosticScreen = screen;
				break;
			}
		}
		
#ifdef SNOOPX_VERBOSE
		if (diagnosticScreen) {
			if (@available(macOS 10.15, *)) {
				CGFloat currentEDR = diagnosticScreen.maximumExtendedDynamicRangeColorComponentValue;
				CGFloat potentialEDR = diagnosticScreen.maximumPotentialExtendedDynamicRangeColorComponentValue;
				NSLog(@"🔆 Display EDR status:");
				NSLog(@"   Current EDR headroom: %.2f", currentEDR);
				NSLog(@"   Potential EDR headroom: %.2f", potentialEDR);
				if (currentEDR <= 1.0) {
					NSLog(@"⚠️ WARNING: Display is NOT currently in HDR mode!");
					NSLog(@"   (Current EDR = %.2f, should be > 1.0 for HDR)", currentEDR);
				} else {
					NSLog(@"✅ Display IS in HDR mode (EDR headroom = %.2f)", currentEDR);
				}
			}
		}
#endif
		
		// Store the display frame for coordinate mapping
		// CRITICAL: We MUST match the ScreenCaptureKit display to the correct NSScreen
		// using the display ID, not by HDR capability
		NSScreen *matchingScreen = nil;
		CGDirectDisplayID targetID = targetDisplay.displayID;
		
		for (NSScreen *screen in [NSScreen screens]) {
			NSDictionary *description = [screen deviceDescription];
			CGDirectDisplayID screenID = [[description valueForKey:@"NSScreenNumber"] unsignedIntValue];
			if (screenID == targetID) {
				matchingScreen = screen;
				break;
			}
		}
		
		if (matchingScreen) {
			currentDisplayFrame = [matchingScreen frame];
#ifdef SNOOPX_VERBOSE
			NSLog(@"✅ Display frame in global AppKit coords: %@", NSStringFromRect(currentDisplayFrame));
			NSLog(@"   Expected buffer size: %.0fx%.0f points (at 2x: %zux%zu pixels)",
				currentDisplayFrame.size.width, currentDisplayFrame.size.height,
				(size_t)(currentDisplayFrame.size.width * 2), (size_t)(currentDisplayFrame.size.height * 2));
			NSLog(@"   Actual buffer size: %zux%zu pixels",
				(size_t)targetDisplay.width, (size_t)targetDisplay.height);
#endif
		} else {
			// Fallback: use buffer dimensions to derive frame
			// This should NOT happen, but if it does, we'll try to make an educated guess
			currentDisplayFrame = NSMakeRect(0, 0, targetDisplay.width / 2.0, targetDisplay.height / 2.0);
			NSLog(@"⚠️ Warning: Could not find matching NSScreen for display ID %u!", targetID);
		}
		
		// Configure stream for HDR using Apple's WWDC24 guidance
		SCStreamConfiguration *config = [[SCStreamConfiguration alloc] init];

		// CRITICAL: Set explicit width/height to native pixel resolution
		// currentDisplayFrame is in points, multiply by backing scale to get pixels
		config.width = (size_t)(currentDisplayFrame.size.width * maxBackingScaleFactor);
		config.height = (size_t)(currentDisplayFrame.size.height * maxBackingScaleFactor);
#ifdef SNOOPX_VERBOSE
		NSLog(@"📐 Setting explicit resolution: %zux%zu pixels (%.0fx%.0f points × %.1fx scale)",
			config.width, config.height,
			currentDisplayFrame.size.width, currentDisplayFrame.size.height,
			maxBackingScaleFactor);
#endif
		
		// WWDC24: Use the HDR preset from Apple
		// This automatically configures pixel format, color space, and EDR settings
		if (@available(macOS 15.0, *)) {
			// macOS 15+ has built-in HDR preset
			// Note: This might be a method like [config setPreset:SCStreamConfigurationPresetHDR]
			// Need to check if this API exists
		}
		
		// Manual configuration for macOS 14 and earlier
		// Use 64-bit RGBA Half-Float for HDR capture
		// CRITICAL: This is essential for capturing HDR values > 1.0!
		// Each pixel is 8 bytes: 4 channels × 2 bytes (Float16)
		config.pixelFormat = kCVPixelFormatType_64RGBAHalf;
		// No explicit colorSpaceName — let system use display's native color space
#ifdef SNOOPX_VERBOSE
		NSLog(@"📐 Pixel format: 64-bit RGBA Half-Float (kCVPixelFormatType_64RGBAHalf)");
		NSLog(@"   Color space: system default (display's native color space for HDR)");
#endif
		
		if (@available(macOS 13.0, *)) {
			config.capturesAudio = NO;
		}
		config.minimumFrameInterval = CMTimeMake(1, 30); // 30 FPS
		config.queueDepth = 5; // Increased for HDR processing
		
		// Critical settings for accurate pixel capture
		config.showsCursor = NO; // Don't show cursor - user wants to see what's UNDER the cursor
		config.scalesToFit = NO; // Don't scale - we want exact pixels
		
		// CRITICAL: Use Automatic capture resolution with no explicit width/height
		// This should capture at the display's natural pixel-perfect resolution
		if (@available(macOS 14.0, *)) {
			config.captureResolution = SCCaptureResolutionAutomatic;
			config.scalesToFit = NO;
			config.preservesAspectRatio = NO;
#ifdef SNOOPX_VERBOSE
			NSLog(@"   captureResolution = Automatic (display native)");
			NSLog(@"   scalesToFit = NO");
			NSLog(@"   preservesAspectRatio = NO");
#endif
		}
		if (@available(macOS 14.2, *)) {
			config.includeChildWindows = YES;
#ifdef SNOOPX_VERBOSE
			NSLog(@"   includeChildWindows = YES");
#endif
		}
		
		// Apply HDR-specific color matrix if available
		if (@available(macOS 15.0, *)) {
			config.colorMatrix = kCMFormatDescriptionColorPrimaries_ITU_R_2020;
#ifdef SNOOPX_VERBOSE
			NSLog(@"   🧪 EXPERIMENT: colorMatrix = ITU-R BT.2020 (HDR standard)");
#endif
		}
		
		// Note: Transfer function (like SMPTE ST 2084 PQ) cannot be set directly on SCStreamConfiguration
		// It's a CVPixelBuffer attachment that's set by the system based on the display's capabilities
		// We can only inspect it, not set it
#ifdef SNOOPX_VERBOSE
		NSLog(@"   ℹ️  Transfer function will be set automatically by the system");
#endif
		
		// These properties ensure we capture ALL visible content
		if (@available(macOS 14.0, *)) {
			config.ignoreShadowsDisplay = NO; // Capture shadows
			config.ignoreShadowsSingleWindow = NO;
		}
		
#ifdef SNOOPX_VERBOSE
		NSLog(@"Stream config: %zux%zu, pixel format: %d, color space: %@",
			config.width, config.height, config.pixelFormat, (__bridge NSString *)config.colorSpaceName);
		NSLog(@"   showsCursor = %d", config.showsCursor);
		NSLog(@"   scalesToFit = %d", config.scalesToFit);
		if (@available(macOS 14.0, *)) {
			NSLog(@"   captureResolution = %ld", (long)config.captureResolution);
		}
		NSLog(@"   queueDepth = %ld", (long)config.queueDepth);
#endif
		
		// Create filter AFTER configuration - capture the ENTIRE display
		// We must include ALL content: desktop, windows, overlays
		self->contentFilter = [[SCContentFilter alloc] initWithDisplay:targetDisplay 
												excludingApplications:@[] 
												exceptingWindows:@[]];
		
		// Create stream
		NSError *streamError = nil;
		self->captureStream = [[SCStream alloc] initWithFilter:self->contentFilter 
													configuration:config 
													delegate:self];
		
		if (streamError) {
			NSLog(@"Error creating stream: %@", streamError);
			return;
		}
		
		// Add stream output
		dispatch_queue_t outputQueue = dispatch_queue_create("com.snoopx.screencapture", DISPATCH_QUEUE_SERIAL);
		[self->captureStream addStreamOutput:self type:SCStreamOutputTypeScreen sampleHandlerQueue:outputQueue error:&streamError];
		
		if (streamError) {
			NSLog(@"Error adding stream output: %@", streamError);
			return;
		}
		
		// Start capture
		[self->captureStream startCaptureWithCompletionHandler:^(NSError *startError) {
			if (startError) {
				NSLog(@"❌ Failed to start ScreenCaptureKit: %@", startError);
				NSLog(@"   Error code: %ld", (long)startError.code);
				NSLog(@"   Error domain: %@", startError.domain);
				self->useScreenCaptureKit = NO;
			} else {
#ifdef SNOOPX_VERBOSE
				NSLog(@"✅ ScreenCaptureKit HDR capture started successfully!");
#endif
				self->useScreenCaptureKit = YES;
			}
		}];
		
		[config release];
	}];
}

- (void) stopScreenCaptureKit API_AVAILABLE(macos(12.3)) {
	if (captureStream) {
#ifdef SNOOPX_VERBOSE
		NSLog(@"Stopping ScreenCaptureKit...");
#endif
		[captureStream stopCaptureWithCompletionHandler:^(NSError *error) {
			if (error) {
				NSLog(@"Error stopping capture: %@", error);
			}
		}];
		[captureStream release];
		captureStream = nil;
	}
	
	if (contentFilter) {
		[contentFilter release];
		contentFilter = nil;
	}
	
	if (currentDisplay) {
		[currentDisplay release];
		currentDisplay = nil;
	}
	
	useScreenCaptureKit = NO;
}

#pragma mark - SCStreamOutput Delegate

- (void)stream:(SCStream *)stream didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer ofType:(SCStreamOutputType)type API_AVAILABLE(macos(12.3)) {
	if (type != SCStreamOutputTypeScreen) {
		return;
	}
	
	CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
	if (!pixelBuffer) {
		return;
	}
	
	// Log pixel buffer details (temporarily unconditional to diagnose color space)
	static dispatch_once_t onceToken;
	dispatch_once(&onceToken, ^{
		OSType format = CVPixelBufferGetPixelFormatType(pixelBuffer);
		CGColorSpaceRef colorSpace = CVImageBufferGetColorSpace(pixelBuffer);
		CFStringRef colorSpaceName = colorSpace ? CGColorSpaceCopyName(colorSpace) : NULL;

		NSLog(@"📺 ScreenCaptureKit buffer details:");
		NSLog(@"   Pixel format: %d (64RGBAHalf=%d, 128RGBAFloat=%d)",
			format, kCVPixelFormatType_64RGBAHalf, kCVPixelFormatType_128RGBAFloat);
		NSLog(@"   Color space: %@", colorSpaceName ? (__bridge NSString *)colorSpaceName : @"(null)");
		NSLog(@"   Size: %zux%zu", CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer));
		if (colorSpaceName) CFRelease(colorSpaceName);

		// Check buffer attachments for color info
		CFTypeRef colorPrimaries = CVBufferGetAttachment(pixelBuffer, kCVImageBufferColorPrimariesKey, NULL);
		CFTypeRef transferFunc = CVBufferGetAttachment(pixelBuffer, kCVImageBufferTransferFunctionKey, NULL);
		CFTypeRef ycbcrMatrix = CVBufferGetAttachment(pixelBuffer, kCVImageBufferYCbCrMatrixKey, NULL);
		NSLog(@"   Color primaries: %@", colorPrimaries ? (__bridge NSString *)colorPrimaries : @"(null)");
		NSLog(@"   Transfer function: %@", transferFunc ? (__bridge NSString *)transferFunc : @"(null)");
		NSLog(@"   YCbCr matrix: %@", ycbcrMatrix ? (__bridge NSString *)ycbcrMatrix : @"(null)");

		// Try to reconstruct color space from attachments
		CFDictionaryRef attachments = CVBufferCopyAttachments(pixelBuffer, kCVAttachmentMode_ShouldPropagate);
		if (attachments) {
			CGColorSpaceRef derivedCS = CVImageBufferCreateColorSpaceFromAttachments(attachments);
			if (derivedCS) {
				CFStringRef derivedName = CGColorSpaceCopyName(derivedCS);
				NSLog(@"   Derived color space: %@", derivedName ? (__bridge NSString *)derivedName : @"(unnamed)");
				NSLog(@"      Model: %ld, Components: %zu, Wide gamut: %@",
					(long)CGColorSpaceGetModel(derivedCS),
					CGColorSpaceGetNumberOfComponents(derivedCS),
					CGColorSpaceIsWideGamutRGB(derivedCS) ? @"YES" : @"NO");
				if (derivedName) CFRelease(derivedName);
				CGColorSpaceRelease(derivedCS);
			} else {
				NSLog(@"   Derived color space: (could not create)");
			}
			CFRelease(attachments);
		} else {
			NSLog(@"   Buffer attachments: (none)");
		}
	});
#ifdef SNOOPX_VERBOSE
	static dispatch_once_t onceTokenVerbose;
	dispatch_once(&onceTokenVerbose, ^{
		OSType format = CVPixelBufferGetPixelFormatType(pixelBuffer);
		CGColorSpaceRef colorSpace = CVImageBufferGetColorSpace(pixelBuffer);
		CFStringRef colorSpaceName = colorSpace ? CGColorSpaceCopyName(colorSpace) : NULL;
		CFTypeRef edrMetadata = CVBufferGetAttachment(pixelBuffer, kCVImageBufferContentLightLevelInfoKey, NULL);
		
		// DETAILED COLOR SPACE INSPECTION
		if (colorSpace) {
			NSLog(@"   📐 Color Space Details:");
			NSLog(@"      Model: %ld", CGColorSpaceGetModel(colorSpace));
			NSLog(@"      Number of components: %zu", CGColorSpaceGetNumberOfComponents(colorSpace));
			NSLog(@"      Is wide gamut: %@", CGColorSpaceIsWideGamutRGB(colorSpace) ? @"YES" : @"NO");
			
			if (@available(macOS 10.12, *)) {
				NSLog(@"      Supports extended range: %@", CGColorSpaceSupportsOutput(colorSpace) ? @"YES" : @"NO");
			}
			
			// Try to get ICC profile data
			CFDataRef iccData = CGColorSpaceCopyICCData(colorSpace);
			if (iccData) {
				NSLog(@"      ICC profile size: %ld bytes", CFDataGetLength(iccData));
				CFRelease(iccData);
			} else {
				NSLog(@"      ICC profile: None (using system default)");
			}
		} else {
			NSLog(@"   ⚠️  Color space is NULL - using default!");
		}
		
		// Check for EDR metadata
		CFTypeRef edrMetadata = CVBufferGetAttachment(pixelBuffer, kCVImageBufferContentLightLevelInfoKey, NULL);
		NSLog(@"   EDR metadata: %@", edrMetadata ? @"Present" : @"None");
		
		// COMPREHENSIVE BUFFER ATTACHMENTS INSPECTION
		NSLog(@"   📋 CVPixelBuffer Attachments:");
		
		// Color primaries
		CFTypeRef colorPrimaries = CVBufferGetAttachment(pixelBuffer, kCVImageBufferColorPrimariesKey, NULL);
		if (colorPrimaries) {
			NSLog(@"      Color Primaries: %@", (__bridge NSString *)colorPrimaries);
		} else {
			NSLog(@"      Color Primaries: Not set");
		}
		
		// Transfer function
		CFTypeRef transferFunction = CVBufferGetAttachment(pixelBuffer, kCVImageBufferTransferFunctionKey, NULL);
		if (transferFunction) {
			NSLog(@"      Transfer Function: %@", (__bridge NSString *)transferFunction);
		} else {
			NSLog(@"      Transfer Function: Not set");
		}
		
		// YCbCr matrix
		CFTypeRef ycbcrMatrix = CVBufferGetAttachment(pixelBuffer, kCVImageBufferYCbCrMatrixKey, NULL);
		if (ycbcrMatrix) {
			NSLog(@"      YCbCr Matrix: %@", (__bridge NSString *)ycbcrMatrix);
		} else {
			NSLog(@"      YCbCr Matrix: Not set (RGB format)");
		}
		
		// Chroma location
		CFTypeRef chromaLocation = CVBufferGetAttachment(pixelBuffer, kCVImageBufferChromaLocationTopFieldKey, NULL);
		if (chromaLocation) {
			NSLog(@"      Chroma Location: %@", (__bridge NSString *)chromaLocation);
		}
		
		// Master display color volume (HDR10 metadata)
		CFTypeRef masteringDisplay = CVBufferGetAttachment(pixelBuffer, kCVImageBufferMasteringDisplayColorVolumeKey, NULL);
		if (masteringDisplay) {
		} else {
		}
		
		// Content light level info (HDR10 metadata)
		if (edrMetadata) {
			NSLog(@"      Content Light Level Info: %@", edrMetadata);
		}
		
		// DIAGNOSTIC 2: Scan entire buffer for HDR values > 1.0
		CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
		void *baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer);
		size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
		size_t width = CVPixelBufferGetWidth(pixelBuffer);
		size_t height = CVPixelBufferGetHeight(pixelBuffer);
		
		// Scan for maximum value
		CGFloat maxR = 0.0, maxG = 0.0, maxB = 0.0;
		size_t maxX = 0, maxY = 0;
		size_t pixelsAbove1 = 0;
		
		BOOL isFloat32 = (format == kCVPixelFormatType_128RGBAFloat);
		BOOL isHalfFloat = (format == kCVPixelFormatType_64RGBAHalf);
		BOOL isYCbCr = (format == '420f' || format == '420v' || format == 'x420');
		
		if (isYCbCr) {
			// YCbCr format - we can't scan it directly for RGB values
			// The conversion to RGB happens later in extractRegionFromHDRBuffer
			NSLog(@"   (HDR values will be checked after YCbCr→RGB conversion)");
		} else if (isFloat32) {
			// Float32 format (16 bytes per pixel)
			for (size_t y = 0; y < height; y += 8) {
				float *row = (float *)((char *)baseAddress + y * bytesPerRow);
				for (size_t x = 0; x < width; x += 8) {
					float *pixel = &row[x * 4];
					CGFloat r = pixel[0];
					CGFloat g = pixel[1];
					CGFloat b = pixel[2];
					
					if (r > maxR) { maxR = r; maxX = x; maxY = y; }
					if (g > maxG) { maxG = g; maxX = x; maxY = y; }
					if (b > maxB) { maxB = b; maxX = x; maxY = y; }
					
					if (r > 1.0 || g > 1.0 || b > 1.0) {
						pixelsAbove1++;
					}
				}
			}
			
			NSLog(@"   Maximum values found: R=%.4f G=%.4f B=%.4f", maxR, maxG, maxB);
			NSLog(@"   Brightest pixel at: (%zu, %zu)", maxX, maxY);
			NSLog(@"   Pixels with values > 1.0: %zu", pixelsAbove1);
			
			if (maxR <= 1.0 && maxG <= 1.0 && maxB <= 1.0) {
				NSLog(@"❌ NO HDR VALUES > 1.0 DETECTED IN BUFFER!");
				NSLog(@"   This means ScreenCaptureKit is TONE-MAPPING the content.");
			} else {
				NSLog(@"✅ HDR VALUES DETECTED! Max component: %.4f", fmax(fmax(maxR, maxG), maxB));
			}
		} else if (isHalfFloat) {
			// Half-float format (8 bytes per pixel)
			for (size_t y = 0; y < height; y += 8) {
				uint16_t *row = (uint16_t *)((char *)baseAddress + y * bytesPerRow);
				for (size_t x = 0; x < width; x += 8) {
					uint16_t *pixel = &row[x * 4];
					CGFloat r = halfFloatToFloat(pixel[0]);
					CGFloat g = halfFloatToFloat(pixel[1]);
					CGFloat b = halfFloatToFloat(pixel[2]);
					
					if (r > maxR) { maxR = r; maxX = x; maxY = y; }
					if (g > maxG) { maxG = g; maxX = x; maxY = y; }
					if (b > maxB) { maxB = b; maxX = x; maxY = y; }
					
					if (r > 1.0 || g > 1.0 || b > 1.0) {
						pixelsAbove1++;
					}
				}
			}
			
			NSLog(@"   Maximum values found: R=%.4f G=%.4f B=%.4f", maxR, maxG, maxB);
			NSLog(@"   Brightest pixel at: (%zu, %zu)", maxX, maxY);
			NSLog(@"   Pixels with values > 1.0: %zu", pixelsAbove1);
			
			if (maxR <= 1.0 && maxG <= 1.0 && maxB <= 1.0) {
				NSLog(@"❌ NO HDR VALUES > 1.0 DETECTED IN BUFFER!");
				NSLog(@"   This means ScreenCaptureKit is TONE-MAPPING the content.");
			} else {
				NSLog(@"✅ HDR VALUES DETECTED! Max component: %.4f", fmax(fmax(maxR, maxG), maxB));
			}
		}
		
		// Also check center pixel (skip for YCbCr)
		if (!isYCbCr) {
			size_t centerX = width / 2;
			size_t centerY = height / 2;
			
			CGFloat sampleR = 0, sampleG = 0, sampleB = 0;
			if (isFloat32) {
				float *pixelData = (float *)((char *)baseAddress + centerY * bytesPerRow);
				float *centerPixel = &pixelData[centerX * 4];
				sampleR = centerPixel[0];
				sampleG = centerPixel[1];
				sampleB = centerPixel[2];
			} else if (isHalfFloat) {
				uint16_t *pixelData = (uint16_t *)((char *)baseAddress + centerY * bytesPerRow);
				uint16_t *centerPixel = &pixelData[centerX * 4];
				sampleR = halfFloatToFloat(centerPixel[0]);
				sampleG = halfFloatToFloat(centerPixel[1]);
				sampleB = halfFloatToFloat(centerPixel[2]);
			}
			
			NSLog(@"   Sample center pixel: R=%.4f G=%.4f B=%.4f", sampleR, sampleG, sampleB);
		}
		
		CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
		
		if (colorSpaceName) CFRelease(colorSpaceName);
	});
#endif
	
	// Retain the new buffer
	CVPixelBufferRetain(pixelBuffer);
	
	// Replace the old buffer
	dispatch_semaphore_wait(pixelBufferSemaphore, DISPATCH_TIME_FOREVER);
	if (latestPixelBuffer) {
		CVPixelBufferRelease(latestPixelBuffer);
	}
	latestPixelBuffer = pixelBuffer;
	dispatch_semaphore_signal(pixelBufferSemaphore);
}

#pragma mark - SCStreamDelegate

- (void)stream:(SCStream *)stream didStopWithError:(NSError *)error API_AVAILABLE(macos(12.3)) {
	NSLog(@"ScreenCaptureKit stream stopped with error: %@", error);
	useScreenCaptureKit = NO;
}

- (BOOL) readHDRPixelFromBuffer:(CVPixelBufferRef)pixelBuffer atX:(size_t)x Y:(size_t)y API_AVAILABLE(macos(12.3)) {
	if (!pixelBuffer) {
		return NO;
	}
	
	OSType pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer);
	size_t width = CVPixelBufferGetWidth(pixelBuffer);
	size_t height = CVPixelBufferGetHeight(pixelBuffer);
	
	// Bounds check
	if (x >= width || y >= height) {
		return NO;
	}
	
	CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
	
	// Handle YCbCr formats - CRITICAL: Don't clamp here, preserve RAW HDR values
	if (pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange ||
		pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange ||
		pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange ||
		pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarFullRange) {
		
		void *yPlane = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
		void *cbcrPlane = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
		size_t yBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
		size_t cbcrBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
		
		BOOL is10Bit = (pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange ||
		                pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarFullRange);
		BOOL isFullRange = (pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange ||
		                    pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarFullRange);
		
		CGFloat yVal, cbVal, crVal;
		
		if (is10Bit) {
			uint16_t *yPtr = (uint16_t *)((char *)yPlane + y * yBytesPerRow);
			uint16_t *cbcrPtr = (uint16_t *)((char *)cbcrPlane + (y / 2) * cbcrBytesPerRow);
			
			// FIXED: Read from UPPER 10 bits (shift right 6)
			// Apple stores 10-bit video in bits [15:6] of uint16_t
			uint16_t yRaw = yPtr[x] >> 6;
			uint16_t cbRaw = cbcrPtr[(x / 2) * 2] >> 6;
			uint16_t crRaw = cbcrPtr[(x / 2) * 2 + 1] >> 6;
			
			yVal = yRaw / 1023.0;
			cbVal = cbRaw / 1023.0;
			crVal = crRaw / 1023.0;
		} else {
			uint8_t *yPtr = (uint8_t *)yPlane + y * yBytesPerRow;
			uint8_t *cbcrPtr = (uint8_t *)cbcrPlane + (y / 2) * cbcrBytesPerRow;
			
			yVal = yPtr[x] / 255.0;
			cbVal = cbcrPtr[(x / 2) * 2] / 255.0;
			crVal = cbcrPtr[(x / 2) * 2 + 1] / 255.0;
		}
		
		// Adjust for video range
		if (!isFullRange) {
			yVal = (yVal - 16.0/255.0) * (255.0/219.0);
			cbVal = (cbVal - 16.0/255.0) * (255.0/224.0);
			crVal = (crVal - 16.0/255.0) * (255.0/224.0);
		}
		
		// Convert to RGB (BT.709) - DON'T CLAMP, preserve values > 1.0!
		cursorRedHDR = yVal + 1.5748 * (crVal - 0.5);
		cursorGreenHDR = yVal - 0.1873 * (cbVal - 0.5) - 0.4681 * (crVal - 0.5);
		cursorBlueHDR = yVal + 1.8556 * (cbVal - 0.5);
		cursorAlphaHDR = 1.0;
		
		// Only clamp for 8-bit display representation
		cursorRed = (size_t)fmin(255, fmax(0, cursorRedHDR * 255.0));
		cursorGreen = (size_t)fmin(255, fmax(0, cursorGreenHDR * 255.0));
		cursorBlue = (size_t)fmin(255, fmax(0, cursorBlueHDR * 255.0));
		cursorAlpha = 255;
		
		CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
		
#ifdef SNOOPX_VERBOSE
		NSLog(@"HDR YCbCr Pixel (%zu, %zu): R=%.4f, G=%.4f, B=%.4f (8-bit: %zu,%zu,%zu)",
			x, y, cursorRedHDR, cursorGreenHDR, cursorBlueHDR,
			cursorRed, cursorGreen, cursorBlue);
#endif
		return YES;
	}
	
	// Handle RGB float formats
	if (pixelFormat != kCVPixelFormatType_64RGBAHalf && pixelFormat != kCVPixelFormatType_128RGBAFloat) {
		NSLog(@"Warning: Unexpected pixel format: %d", pixelFormat);
		CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
		return NO;
	}
	
	void *baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer);
	size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
	BOOL isFloat32 = (pixelFormat == kCVPixelFormatType_128RGBAFloat);
	
	// Read pixel based on format
	if (isFloat32) {
		float *pixelData = (float *)((char *)baseAddress + y * bytesPerRow);
		float *pixel = &pixelData[x * 4];

		// Don't clamp - show actual values including negatives (like Apple's Digital Color Meter)
		cursorRedHDR = pixel[0];
		cursorGreenHDR = pixel[1];
		cursorBlueHDR = pixel[2];
		cursorAlphaHDR = pixel[3];
	} else {
		uint16_t *pixelData = (uint16_t *)((char *)baseAddress + y * bytesPerRow);
		uint16_t *pixel = &pixelData[x * 4];
		
		// Don't clamp - show actual values including negatives (like Apple's Digital Color Meter)
		cursorRedHDR = halfFloatToFloat(pixel[0]);
		cursorGreenHDR = halfFloatToFloat(pixel[1]);
		cursorBlueHDR = halfFloatToFloat(pixel[2]);
		cursorAlphaHDR = halfFloatToFloat(pixel[3]);
	}

	// Log what was READ from the capture buffer at cursor position

	// Convert to 8-bit (clamp to 0-255 range for SDR display)
	cursorRed = (size_t)fmax(0, fmin(255, cursorRedHDR * 255.0));
	cursorGreen = (size_t)fmax(0, fmin(255, cursorGreenHDR * 255.0));
	cursorBlue = (size_t)fmax(0, fmin(255, cursorBlueHDR * 255.0));
	cursorAlpha = (size_t)fmax(0, fmin(255, cursorAlphaHDR * 255.0));
	
	CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);

#ifdef SNOOPX_VERBOSE
	NSLog(@"HDR RGB Pixel (%zu, %zu): R=%.4f, G=%.4f, B=%.4f (8-bit: %zu,%zu,%zu)",
		x, y, cursorRedHDR, cursorGreenHDR, cursorBlueHDR,
		cursorRed, cursorGreen, cursorBlue);
#endif

	return YES;
}

- (NSString*) createStatisticsInfoString
{
	return [[NSString alloc] initWithCString:statisticsInfoString encoding:NSASCIIStringEncoding];
}

@end
