2 * Copyright 2000 Corel Corporation
3 * Copyright 2006 Marcus Meissner
4 * Copyright 2006 CodeWeavers, Aric Stewart
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 #include "wine/port.h"
23 #include "wine/library.h"
33 #include "gphoto2_i.h"
34 #include "wine/debug.h"
36 WINE_DEFAULT_DEBUG_CHANNEL(twain
);
39 static void *libjpeg_handle
;
40 #define MAKE_FUNCPTR(f) static typeof(f) * p##f
41 MAKE_FUNCPTR(jpeg_std_error
);
42 MAKE_FUNCPTR(jpeg_CreateDecompress
);
43 MAKE_FUNCPTR(jpeg_read_header
);
44 MAKE_FUNCPTR(jpeg_start_decompress
);
45 MAKE_FUNCPTR(jpeg_read_scanlines
);
46 MAKE_FUNCPTR(jpeg_finish_decompress
);
47 MAKE_FUNCPTR(jpeg_destroy_decompress
);
50 static void *load_libjpeg(void)
52 if((libjpeg_handle
= wine_dlopen(SONAME_LIBJPEG
, RTLD_NOW
, NULL
, 0)) != NULL
) {
54 #define LOAD_FUNCPTR(f) \
55 if((p##f = wine_dlsym(libjpeg_handle, #f, NULL, 0)) == NULL) { \
56 libjpeg_handle = NULL; \
60 LOAD_FUNCPTR(jpeg_std_error
);
61 LOAD_FUNCPTR(jpeg_CreateDecompress
);
62 LOAD_FUNCPTR(jpeg_read_header
);
63 LOAD_FUNCPTR(jpeg_start_decompress
);
64 LOAD_FUNCPTR(jpeg_read_scanlines
);
65 LOAD_FUNCPTR(jpeg_finish_decompress
);
66 LOAD_FUNCPTR(jpeg_destroy_decompress
);
69 return libjpeg_handle
;
73 /* for the jpeg decompressor source manager. */
74 static void _jpeg_init_source(j_decompress_ptr cinfo
) { }
76 static boolean
_jpeg_fill_input_buffer(j_decompress_ptr cinfo
) {
77 ERR("(), should not get here.\n");
81 static void _jpeg_skip_input_data(j_decompress_ptr cinfo
,long num_bytes
) {
82 TRACE("Skipping %ld bytes...\n", num_bytes
);
83 cinfo
->src
->next_input_byte
+= num_bytes
;
84 cinfo
->src
->bytes_in_buffer
-= num_bytes
;
87 static boolean
_jpeg_resync_to_restart(j_decompress_ptr cinfo
, int desired
) {
88 ERR("(desired=%d), should not get here.\n",desired
);
91 static void _jpeg_term_source(j_decompress_ptr cinfo
) { }
94 /* DG_IMAGE/DAT_CIECOLOR/MSG_GET */
95 TW_UINT16
GPHOTO2_CIEColorGet (pTW_IDENTITY pOrigin
,
103 /* DG_IMAGE/DAT_EXTIMAGEINFO/MSG_GET */
104 TW_UINT16
GPHOTO2_ExtImageInfoGet (pTW_IDENTITY pOrigin
,
112 /* DG_IMAGE/DAT_GRAYRESPONSE/MSG_RESET */
113 TW_UINT16
GPHOTO2_GrayResponseReset (pTW_IDENTITY pOrigin
,
121 /* DG_IMAGE/DAT_GRAYRESPONSE/MSG_SET */
122 TW_UINT16
GPHOTO2_GrayResponseSet (pTW_IDENTITY pOrigin
,
130 /* DG_IMAGE/DAT_IMAGEFILEXFER/MSG_GET */
131 TW_UINT16
GPHOTO2_ImageFileXferGet (pTW_IDENTITY pOrigin
,
140 static TW_UINT16
_get_image_and_startup_jpeg(void) {
141 const char *folder
= NULL
, *filename
= NULL
;
142 struct gphoto2_file
*file
;
143 const unsigned char *filedata
;
144 unsigned long filesize
;
147 if (activeDS
.file
) /* Already loaded. */
150 if(!libjpeg_handle
) {
151 if(!load_libjpeg()) {
152 FIXME("Failed reading JPEG because unable to find %s\n", SONAME_LIBJPEG
);
158 LIST_FOR_EACH_ENTRY( file
, &activeDS
.files
, struct gphoto2_file
, entry
) {
159 if (strstr(file
->filename
,".JPG") || strstr(file
->filename
,".jpg")) {
160 filename
= file
->filename
;
161 folder
= file
->folder
;
162 TRACE("downloading %s/%s\n", folder
, filename
);
163 if (file
->download
) {
164 file
->download
= FALSE
; /* mark as done */
169 gp_file_new (&activeDS
.file
);
170 ret
= gp_camera_file_get(activeDS
.camera
, folder
, filename
, GP_FILE_TYPE_NORMAL
,
171 activeDS
.file
, activeDS
.context
);
173 FIXME("Failed to get file?\n");
174 activeDS
.twCC
= TWCC_SEQERROR
;
177 ret
= gp_file_get_data_and_size (activeDS
.file
, (const char**)&filedata
, &filesize
);
179 FIXME("Failed to get file data?\n");
180 activeDS
.twCC
= TWCC_SEQERROR
;
184 /* This is basically so we can use in-memory data for jpeg decompression.
185 * We need to have all the functions.
187 activeDS
.xjsm
.next_input_byte
= filedata
;
188 activeDS
.xjsm
.bytes_in_buffer
= filesize
;
189 activeDS
.xjsm
.init_source
= _jpeg_init_source
;
190 activeDS
.xjsm
.fill_input_buffer
= _jpeg_fill_input_buffer
;
191 activeDS
.xjsm
.skip_input_data
= _jpeg_skip_input_data
;
192 activeDS
.xjsm
.resync_to_restart
= _jpeg_resync_to_restart
;
193 activeDS
.xjsm
.term_source
= _jpeg_term_source
;
195 activeDS
.jd
.err
= pjpeg_std_error(&activeDS
.jerr
);
196 /* jpeg_create_decompress is a macro that expands to jpeg_CreateDecompress - see jpeglib.h
197 * jpeg_create_decompress(&jd); */
198 pjpeg_CreateDecompress(&activeDS
.jd
, JPEG_LIB_VERSION
, (size_t) sizeof(struct jpeg_decompress_struct
));
199 activeDS
.jd
.src
= &activeDS
.xjsm
;
200 ret
=pjpeg_read_header(&activeDS
.jd
,TRUE
);
201 activeDS
.jd
.out_color_space
= JCS_RGB
;
202 pjpeg_start_decompress(&activeDS
.jd
);
203 if (ret
!= JPEG_HEADER_OK
) {
204 ERR("Jpeg image in stream has bad format, read header returned %d.\n",ret
);
205 gp_file_unref (activeDS
.file
);
206 activeDS
.file
= NULL
;
213 /* DG_IMAGE/DAT_IMAGEINFO/MSG_GET */
214 TW_UINT16
GPHOTO2_ImageInfoGet (pTW_IDENTITY pOrigin
,
218 pTW_IMAGEINFO pImageInfo
= (pTW_IMAGEINFO
) pData
;
220 TRACE("DG_IMAGE/DAT_IMAGEINFO/MSG_GET\n");
222 if (activeDS
.currentState
!= 6 && activeDS
.currentState
!= 7) {
223 activeDS
.twCC
= TWCC_SEQERROR
;
226 if (TWRC_SUCCESS
!= _get_image_and_startup_jpeg()) {
227 FIXME("Failed to get an image\n");
228 activeDS
.twCC
= TWCC_SEQERROR
;
231 if (activeDS
.currentState
== 6)
233 /* return general image description information about the image about to be transferred */
234 TRACE("Getting parameters\n");
236 TRACE("activeDS.jd.output_width = %d\n", activeDS
.jd
.output_width
);
237 TRACE("activeDS.jd.output_height = %d\n", activeDS
.jd
.output_height
);
238 pImageInfo
->Compression
= TWCP_NONE
;
239 pImageInfo
->SamplesPerPixel
= 3;
240 pImageInfo
->BitsPerSample
[0]= 8;
241 pImageInfo
->BitsPerSample
[1]= 8;
242 pImageInfo
->BitsPerSample
[2]= 8;
243 pImageInfo
->PixelType
= TWPT_RGB
;
244 pImageInfo
->Planar
= FALSE
; /* R-G-B is chunky! */
245 pImageInfo
->XResolution
.Whole
= -1;
246 pImageInfo
->XResolution
.Frac
= 0;
247 pImageInfo
->YResolution
.Whole
= -1;
248 pImageInfo
->YResolution
.Frac
= 0;
249 pImageInfo
->ImageWidth
= activeDS
.jd
.output_width
;
250 pImageInfo
->ImageLength
= activeDS
.jd
.output_height
;
251 pImageInfo
->BitsPerPixel
= 24;
258 /* DG_IMAGE/DAT_IMAGELAYOUT/MSG_GET */
259 TW_UINT16
GPHOTO2_ImageLayoutGet (pTW_IDENTITY pOrigin
,
267 /* DG_IMAGE/DAT_IMAGELAYOUT/MSG_GETDEFAULT */
268 TW_UINT16
GPHOTO2_ImageLayoutGetDefault (pTW_IDENTITY pOrigin
,
276 /* DG_IMAGE/DAT_IMAGELAYOUT/MSG_RESET */
277 TW_UINT16
GPHOTO2_ImageLayoutReset (pTW_IDENTITY pOrigin
,
285 /* DG_IMAGE/DAT_IMAGELAYOUT/MSG_SET */
286 TW_UINT16
GPHOTO2_ImageLayoutSet (pTW_IDENTITY pOrigin
,
294 /* DG_IMAGE/DAT_IMAGEMEMXFER/MSG_GET */
295 TW_UINT16
GPHOTO2_ImageMemXferGet (pTW_IDENTITY pOrigin
,
299 TW_UINT16 twRC
= TWRC_SUCCESS
;
300 pTW_IMAGEMEMXFER pImageMemXfer
= (pTW_IMAGEMEMXFER
) pData
;
305 TRACE ("DG_IMAGE/DAT_IMAGEMEMXFER/MSG_GET\n");
306 if (activeDS
.currentState
< 6 || activeDS
.currentState
> 7) {
307 activeDS
.twCC
= TWCC_SEQERROR
;
310 TRACE("pImageMemXfer.Compression is %d\n", pImageMemXfer
->Compression
);
311 if (activeDS
.currentState
== 6) {
312 if (TWRC_SUCCESS
!= _get_image_and_startup_jpeg()) {
313 FIXME("Failed to get an image\n");
314 activeDS
.twCC
= TWCC_SEQERROR
;
318 if (!activeDS
.progressWnd
)
319 activeDS
.progressWnd
= TransferringDialogBox(NULL
,0);
320 TransferringDialogBox(activeDS
.progressWnd
,0);
322 activeDS
.currentState
= 7;
324 if (!activeDS
.file
) {
325 activeDS
.twCC
= TWRC_SUCCESS
;
326 return TWRC_XFERDONE
;
330 if (pImageMemXfer
->Memory
.Flags
& TWMF_HANDLE
) {
331 FIXME("Memory Handle, may not be locked correctly\n");
332 buffer
= LocalLock(pImageMemXfer
->Memory
.TheMem
);
334 buffer
= pImageMemXfer
->Memory
.TheMem
;
336 memset(buffer
,0,pImageMemXfer
->Memory
.Length
);
337 curoff
= 0; readrows
= 0;
338 pImageMemXfer
->YOffset
= activeDS
.jd
.output_scanline
;
339 pImageMemXfer
->XOffset
= 0; /* we do whole strips */
340 while ((activeDS
.jd
.output_scanline
<activeDS
.jd
.output_height
) &&
341 ((pImageMemXfer
->Memory
.Length
- curoff
) > activeDS
.jd
.output_width
*activeDS
.jd
.output_components
)
343 JSAMPROW row
= buffer
+curoff
;
344 int x
= pjpeg_read_scanlines(&activeDS
.jd
,&row
,1);
346 FIXME("failed to read current scanline?\n");
350 curoff
+= activeDS
.jd
.output_width
*activeDS
.jd
.output_components
;
352 pImageMemXfer
->Compression
= TWCP_NONE
;
353 pImageMemXfer
->BytesPerRow
= activeDS
.jd
.output_components
* activeDS
.jd
.output_width
;
354 pImageMemXfer
->Rows
= readrows
;
355 pImageMemXfer
->Columns
= activeDS
.jd
.output_width
; /* we do whole strips */
356 pImageMemXfer
->BytesWritten
= curoff
;
358 TransferringDialogBox(activeDS
.progressWnd
,0);
360 if (activeDS
.jd
.output_scanline
== activeDS
.jd
.output_height
) {
361 pjpeg_finish_decompress(&activeDS
.jd
);
362 pjpeg_destroy_decompress(&activeDS
.jd
);
363 gp_file_unref (activeDS
.file
);
364 activeDS
.file
= NULL
;
365 TRACE("xfer is done!\n");
367 /*TransferringDialogBox(activeDS.progressWnd, -1);*/
368 twRC
= TWRC_XFERDONE
;
370 activeDS
.twCC
= TWRC_SUCCESS
;
371 if (pImageMemXfer
->Memory
.Flags
& TWMF_HANDLE
)
372 LocalUnlock(pImageMemXfer
->Memory
.TheMem
);
379 /* DG_IMAGE/DAT_IMAGENATIVEXFER/MSG_GET */
380 TW_UINT16
GPHOTO2_ImageNativeXferGet (pTW_IDENTITY pOrigin
,
384 pTW_UINT32 pHandle
= (pTW_UINT32
) pData
;
388 JSAMPROW samprow
, oldsamprow
;
391 FIXME("DG_IMAGE/DAT_IMAGENATIVEXFER/MSG_GET: implemented, but expect program crash due to DIB.\n");
393 /* NOTE NOTE NOTE NOTE NOTE NOTE NOTE
395 * While this is a mandatory transfer mode and this function
396 * is correctly implemented and fully works, the calling program
397 * will likely crash after calling.
399 * Reason is that there is a lot of example code that does:
400 * bmpinfo = GlobalLock(hBITMAP); ... pointer access to bmpinfo
402 * Our current HBITMAP handles do not support getting GlobalLocked -> App Crash
404 * This needs a GDI Handle rewrite, at least for DIB sections.
407 if (activeDS
.currentState
!= 6) {
408 activeDS
.twCC
= TWCC_SEQERROR
;
411 if (TWRC_SUCCESS
!= _get_image_and_startup_jpeg()) {
412 FIXME("Failed to get an image\n");
413 activeDS
.twCC
= TWCC_OPERATIONERROR
;
416 TRACE("Acquiring image %dx%dx%d bits from gphoto.\n",
417 activeDS
.jd
.output_width
, activeDS
.jd
.output_height
,
418 activeDS
.jd
.output_components
*8);
419 ZeroMemory (&bmpInfo
, sizeof (BITMAPINFO
));
420 bmpInfo
.bmiHeader
.biSize
= sizeof (BITMAPINFOHEADER
);
421 bmpInfo
.bmiHeader
.biWidth
= activeDS
.jd
.output_width
;
422 bmpInfo
.bmiHeader
.biHeight
= -activeDS
.jd
.output_height
;
423 bmpInfo
.bmiHeader
.biPlanes
= 1;
424 bmpInfo
.bmiHeader
.biBitCount
= activeDS
.jd
.output_components
*8;
425 bmpInfo
.bmiHeader
.biCompression
= BI_RGB
;
426 bmpInfo
.bmiHeader
.biSizeImage
= 0;
427 bmpInfo
.bmiHeader
.biXPelsPerMeter
= 0;
428 bmpInfo
.bmiHeader
.biYPelsPerMeter
= 0;
429 bmpInfo
.bmiHeader
.biClrUsed
= 0;
430 bmpInfo
.bmiHeader
.biClrImportant
= 0;
431 hDIB
= CreateDIBSection ((dc
= GetDC(activeDS
.hwndOwner
)), &bmpInfo
,
432 DIB_RGB_COLORS
, (LPVOID
)&bits
, 0, 0);
434 FIXME("Failed creating DIB.\n");
435 gp_file_unref (activeDS
.file
);
436 activeDS
.file
= NULL
;
437 activeDS
.twCC
= TWCC_LOWMEMORY
;
440 samprow
= HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY
,activeDS
.jd
.output_width
*activeDS
.jd
.output_components
);
441 oldsamprow
= samprow
;
442 while ( activeDS
.jd
.output_scanline
<activeDS
.jd
.output_height
) {
443 int i
, x
= pjpeg_read_scanlines(&activeDS
.jd
,&samprow
,1);
445 FIXME("failed to read current scanline?\n");
448 /* We have to convert from RGB to BGR, see MSDN/ BITMAPINFOHEADER */
449 for(i
=0;i
<activeDS
.jd
.output_width
;i
++,samprow
+=activeDS
.jd
.output_components
) {
450 *(bits
++) = *(samprow
+2);
451 *(bits
++) = *(samprow
+1);
452 *(bits
++) = *(samprow
);
454 bits
= (LPBYTE
)(((UINT_PTR
)bits
+ 3) & ~3);
455 samprow
= oldsamprow
;
457 HeapFree (GetProcessHeap(), 0, samprow
);
458 gp_file_unref (activeDS
.file
);
459 activeDS
.file
= NULL
;
460 ReleaseDC (activeDS
.hwndOwner
, dc
);
461 *pHandle
= (UINT_PTR
)hDIB
;
462 activeDS
.twCC
= TWCC_SUCCESS
;
463 activeDS
.currentState
= 7;
464 return TWRC_XFERDONE
;
470 /* DG_IMAGE/DAT_JPEGCOMPRESSION/MSG_GET */
471 TW_UINT16
GPHOTO2_JPEGCompressionGet (pTW_IDENTITY pOrigin
,
479 /* DG_IMAGE/DAT_JPEGCOMPRESSION/MSG_GETDEFAULT */
480 TW_UINT16
GPHOTO2_JPEGCompressionGetDefault (pTW_IDENTITY pOrigin
,
489 /* DG_IMAGE/DAT_JPEGCOMPRESSION/MSG_RESET */
490 TW_UINT16
GPHOTO2_JPEGCompressionReset (pTW_IDENTITY pOrigin
,
498 /* DG_IMAGE/DAT_JPEGCOMPRESSION/MSG_SET */
499 TW_UINT16
GPHOTO2_JPEGCompressionSet (pTW_IDENTITY pOrigin
,
507 /* DG_IMAGE/DAT_PALETTE8/MSG_GET */
508 TW_UINT16
GPHOTO2_Palette8Get (pTW_IDENTITY pOrigin
,
516 /* DG_IMAGE/DAT_PALETTE8/MSG_GETDEFAULT */
517 TW_UINT16
GPHOTO2_Palette8GetDefault (pTW_IDENTITY pOrigin
,
525 /* DG_IMAGE/DAT_PALETTE8/MSG_RESET */
526 TW_UINT16
GPHOTO2_Palette8Reset (pTW_IDENTITY pOrigin
,
534 /* DG_IMAGE/DAT_PALETTE8/MSG_SET */
535 TW_UINT16
GPHOTO2_Palette8Set (pTW_IDENTITY pOrigin
,
543 /* DG_IMAGE/DAT_RGBRESPONSE/MSG_RESET */
544 TW_UINT16
GPHOTO2_RGBResponseReset (pTW_IDENTITY pOrigin
,
552 /* DG_IMAGE/DAT_RGBRESPONSE/MSG_SET */
553 TW_UINT16
GPHOTO2_RGBResponseSet (pTW_IDENTITY pOrigin
,
563 _get_gphoto2_file_as_DIB(
564 const char *folder
, const char *filename
, CameraFileType type
,
565 HWND hwnd
, HBITMAP
*hDIB
567 const unsigned char *filedata
;
568 unsigned long filesize
;
571 struct jpeg_source_mgr xjsm
;
572 struct jpeg_decompress_struct jd
;
573 struct jpeg_error_mgr jerr
;
577 JSAMPROW samprow
, oldsamprow
;
579 if(!libjpeg_handle
) {
580 if(!load_libjpeg()) {
581 FIXME("Failed reading JPEG because unable to find %s\n", SONAME_LIBJPEG
);
588 ret
= gp_camera_file_get(activeDS
.camera
, folder
, filename
, type
, file
, activeDS
.context
);
590 FIXME("Failed to get file?\n");
591 gp_file_unref (file
);
594 ret
= gp_file_get_data_and_size (file
, (const char**)&filedata
, &filesize
);
596 FIXME("Failed to get file data?\n");
600 /* FIXME: Actually we might get other types than JPEG ... But only handle JPEG for now */
601 if (filedata
[0] != 0xff) {
602 ERR("File %s/%s might not be JPEG, cannot decode!\n", folder
, filename
);
605 /* This is basically so we can use in-memory data for jpeg decompression.
606 * We need to have all the functions.
608 xjsm
.next_input_byte
= filedata
;
609 xjsm
.bytes_in_buffer
= filesize
;
610 xjsm
.init_source
= _jpeg_init_source
;
611 xjsm
.fill_input_buffer
= _jpeg_fill_input_buffer
;
612 xjsm
.skip_input_data
= _jpeg_skip_input_data
;
613 xjsm
.resync_to_restart
= _jpeg_resync_to_restart
;
614 xjsm
.term_source
= _jpeg_term_source
;
616 jd
.err
= pjpeg_std_error(&jerr
);
617 /* jpeg_create_decompress is a macro that expands to jpeg_CreateDecompress - see jpeglib.h
618 * jpeg_create_decompress(&jd); */
619 pjpeg_CreateDecompress(&jd
, JPEG_LIB_VERSION
, (size_t) sizeof(struct jpeg_decompress_struct
));
621 ret
=pjpeg_read_header(&jd
,TRUE
);
622 jd
.out_color_space
= JCS_RGB
;
623 pjpeg_start_decompress(&jd
);
624 if (ret
!= JPEG_HEADER_OK
) {
625 ERR("Jpeg image in stream has bad format, read header returned %d.\n",ret
);
626 gp_file_unref (file
);
630 ZeroMemory (&bmpInfo
, sizeof (BITMAPINFO
));
631 bmpInfo
.bmiHeader
.biSize
= sizeof (BITMAPINFOHEADER
);
632 bmpInfo
.bmiHeader
.biWidth
= jd
.output_width
;
633 bmpInfo
.bmiHeader
.biHeight
= -jd
.output_height
;
634 bmpInfo
.bmiHeader
.biPlanes
= 1;
635 bmpInfo
.bmiHeader
.biBitCount
= jd
.output_components
*8;
636 bmpInfo
.bmiHeader
.biCompression
= BI_RGB
;
637 bmpInfo
.bmiHeader
.biSizeImage
= 0;
638 bmpInfo
.bmiHeader
.biXPelsPerMeter
= 0;
639 bmpInfo
.bmiHeader
.biYPelsPerMeter
= 0;
640 bmpInfo
.bmiHeader
.biClrUsed
= 0;
641 bmpInfo
.bmiHeader
.biClrImportant
= 0;
642 *hDIB
= CreateDIBSection ((dc
= GetDC(hwnd
)), &bmpInfo
, DIB_RGB_COLORS
, (LPVOID
)&bits
, 0, 0);
644 FIXME("Failed creating DIB.\n");
645 gp_file_unref (file
);
648 samprow
= HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY
,jd
.output_width
*jd
.output_components
);
649 oldsamprow
= samprow
;
650 while ( jd
.output_scanline
<jd
.output_height
) {
651 int i
, x
= pjpeg_read_scanlines(&jd
,&samprow
,1);
653 FIXME("failed to read current scanline?\n");
656 /* We have to convert from RGB to BGR, see MSDN/ BITMAPINFOHEADER */
657 for(i
=0;i
<jd
.output_width
;i
++,samprow
+=jd
.output_components
) {
658 *(bits
++) = *(samprow
+2);
659 *(bits
++) = *(samprow
+1);
660 *(bits
++) = *(samprow
);
662 bits
= (LPBYTE
)(((UINT_PTR
)bits
+ 3) & ~3);
663 samprow
= oldsamprow
;
665 if (hwnd
) ReleaseDC (hwnd
, dc
);
666 HeapFree (GetProcessHeap(), 0, samprow
);
667 gp_file_unref (file
);