Skip to content

Commit 17db161

Browse files
committed
fix: Use sRGB color space for screen capture.
1 parent c22555a commit 17db161

1 file changed

Lines changed: 78 additions & 25 deletions

File tree

src/screengrab.c

Lines changed: 78 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,28 @@
1515
#include <string.h>
1616
#endif
1717

18+
#if defined(IS_MACOSX)
19+
#elif defined(IS_WINDOWS)
20+
static void destroyMMBitmapWindowsDIB(char *bitmapBuffer, void *hint)
21+
{
22+
if (hint != NULL) {
23+
DeleteObject((HGDIOBJ)hint);
24+
}
25+
}
26+
#endif
27+
1828
MMBitmapRef copyMMBitmapFromDisplayInRect(MMRect rect)
1929
{
2030
#if defined(IS_MACOSX)
2131

2232
MMBitmapRef bitmap = NULL;
33+
CGContextRef context = NULL;
34+
CGColorSpaceRef colorSpace = NULL;
2335
uint8_t *buffer = NULL;
24-
size_t bufferSize = 0;
36+
const size_t width = rect.size.width;
37+
const size_t height = rect.size.height;
38+
const size_t bytesPerPixel = 4;
39+
const size_t bytesPerRow = width * bytesPerPixel;
2540

2641
CGDirectDisplayID displayID = CGMainDisplayID();
2742

@@ -33,23 +48,54 @@ MMBitmapRef copyMMBitmapFromDisplayInRect(MMRect rect)
3348

3449
if (!image) { return NULL; }
3550

36-
CFDataRef imageData = CGDataProviderCopyData(CGImageGetDataProvider(image));
51+
colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
52+
if (colorSpace == NULL) {
53+
CGImageRelease(image);
54+
return NULL;
55+
}
3756

38-
if (!imageData) { return NULL; }
57+
buffer = calloc(height, bytesPerRow);
58+
if (buffer == NULL) {
59+
CGColorSpaceRelease(colorSpace);
60+
CGImageRelease(image);
61+
return NULL;
62+
}
3963

40-
bufferSize = CFDataGetLength(imageData);
41-
buffer = malloc(bufferSize);
64+
context = CGBitmapContextCreate(buffer,
65+
width,
66+
height,
67+
8,
68+
bytesPerRow,
69+
colorSpace,
70+
kCGImageAlphaNoneSkipFirst |
71+
kCGBitmapByteOrder32Little);
72+
if (context == NULL) {
73+
free(buffer);
74+
CGColorSpaceRelease(colorSpace);
75+
CGImageRelease(image);
76+
return NULL;
77+
}
4278

43-
CFDataGetBytes(imageData, CFRangeMake(0,bufferSize), buffer);
79+
/* RobotJS bitmaps use a top-left origin. Flip the Quartz context so the
80+
* captured image keeps that coordinate system after color conversion. */
81+
CGContextTranslateCTM(context, 0, height);
82+
CGContextScaleCTM(context, 1, -1);
83+
CGContextSetBlendMode(context, kCGBlendModeCopy);
84+
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
85+
CGContextDrawImage(context, CGRectMake(0, 0, width, height), image);
4486

4587
bitmap = createMMBitmap(buffer,
46-
CGImageGetWidth(image),
47-
CGImageGetHeight(image),
48-
CGImageGetBytesPerRow(image),
49-
CGImageGetBitsPerPixel(image),
50-
CGImageGetBitsPerPixel(image) / 8);
88+
width,
89+
height,
90+
bytesPerRow,
91+
32,
92+
4);
93+
if (bitmap == NULL) {
94+
free(buffer);
95+
}
5196

52-
CFRelease(imageData);
97+
CGContextRelease(context);
98+
CGColorSpaceRelease(colorSpace);
5399

54100
CGImageRelease(image);
55101

@@ -85,6 +131,7 @@ MMBitmapRef copyMMBitmapFromDisplayInRect(MMRect rect)
85131
void *data;
86132
HDC screen = NULL, screenMem = NULL;
87133
HBITMAP dib;
134+
HGDIOBJ previousObject = NULL;
88135
BITMAPINFO bi;
89136

90137
/* Initialize bitmap info. */
@@ -105,10 +152,15 @@ MMBitmapRef copyMMBitmapFromDisplayInRect(MMRect rect)
105152

106153
/* Get screen data in display device context. */
107154
dib = CreateDIBSection(screen, &bi, DIB_RGB_COLORS, &data, NULL, 0);
155+
if (dib == NULL || data == NULL) {
156+
if (dib != NULL) DeleteObject(dib);
157+
ReleaseDC(NULL, screen);
158+
return NULL;
159+
}
108160

109161
/* Copy the data into a bitmap struct. */
110162
if ((screenMem = CreateCompatibleDC(screen)) == NULL ||
111-
SelectObject(screenMem, dib) == NULL ||
163+
(previousObject = SelectObject(screenMem, dib)) == NULL ||
112164
!BitBlt(screenMem,
113165
(int)0,
114166
(int)0,
@@ -127,21 +179,22 @@ MMBitmapRef copyMMBitmapFromDisplayInRect(MMRect rect)
127179
return NULL;
128180
}
129181

130-
bitmap = createMMBitmap(NULL,
131-
rect.size.width,
132-
rect.size.height,
133-
4 * rect.size.width,
134-
(uint8_t)bi.bmiHeader.biBitCount,
135-
4);
136-
137-
/* Copy the data to our pixel buffer. */
138-
if (bitmap != NULL) {
139-
bitmap->imageBuffer = malloc(bitmap->bytewidth * bitmap->height);
140-
memcpy(bitmap->imageBuffer, data, bitmap->bytewidth * bitmap->height);
182+
bitmap = createMMBitmapWithCleanup((uint8_t *)data,
183+
rect.size.width,
184+
rect.size.height,
185+
4 * rect.size.width,
186+
(uint8_t)bi.bmiHeader.biBitCount,
187+
4,
188+
destroyMMBitmapWindowsDIB,
189+
dib);
190+
if (previousObject != NULL) {
191+
SelectObject(screenMem, previousObject);
192+
}
193+
if (bitmap == NULL) {
194+
DeleteObject(dib);
141195
}
142196

143197
ReleaseDC(NULL, screen);
144-
DeleteObject(dib);
145198
DeleteDC(screenMem);
146199

147200
return bitmap;

0 commit comments

Comments
 (0)