Sync with trunk (r47116), hopefully without breaking anything.
[reactos.git] / subsystems / win32 / win32k / eng / surface.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * PURPOSE: GDI Driver Surace Functions
5 * FILE: subsys/win32k/eng/surface.c
6 * PROGRAMER: Jason Filby
7 * REVISION HISTORY:
8 * 3/7/1999: Created
9 * 9/11/2000: Updated to handle real pixel packed bitmaps (UPDATE TO DATE COMPLETED)
10 * TESTING TO BE DONE:
11 * - Create a GDI bitmap with all formats, perform all drawing operations on them, render to VGA surface
12 * refer to \test\microwin\src\engine\devdraw.c for info on correct pixel plotting for various formats
13 */
14
15 #include <win32k.h>
16
17 #define NDEBUG
18 #include <debug.h>
19
20 enum Rle_EscapeCodes
21 {
22 RLE_EOL = 0, /* End of line */
23 RLE_END = 1, /* End of bitmap */
24 RLE_DELTA = 2 /* Delta */
25 };
26
27 INT FASTCALL BitsPerFormat(ULONG Format)
28 {
29 switch (Format)
30 {
31 case BMF_1BPP:
32 return 1;
33
34 case BMF_4BPP:
35 /* Fall through */
36 case BMF_4RLE:
37 return 4;
38
39 case BMF_8BPP:
40 /* Fall through */
41 case BMF_8RLE:
42 return 8;
43
44 case BMF_16BPP:
45 return 16;
46
47 case BMF_24BPP:
48 return 24;
49
50 case BMF_32BPP:
51 return 32;
52
53 default:
54 return 0;
55 }
56 }
57
58 ULONG FASTCALL BitmapFormat(WORD Bits, DWORD Compression)
59 {
60 switch (Compression)
61 {
62 case BI_RGB:
63 /* Fall through */
64 case BI_BITFIELDS:
65 switch (Bits)
66 {
67 case 1:
68 return BMF_1BPP;
69 case 4:
70 return BMF_4BPP;
71 case 8:
72 return BMF_8BPP;
73 case 16:
74 return BMF_16BPP;
75 case 24:
76 return BMF_24BPP;
77 case 32:
78 return BMF_32BPP;
79 }
80 return 0;
81
82 case BI_RLE4:
83 return BMF_4RLE;
84
85 case BI_RLE8:
86 return BMF_8RLE;
87
88 default:
89 return 0;
90 }
91 }
92
93 BOOL INTERNAL_CALL
94 SURFACE_Cleanup(PVOID ObjectBody)
95 {
96 PSURFACE psurf = (PSURFACE)ObjectBody;
97 PVOID pvBits = psurf->SurfObj.pvBits;
98
99 /* If this is an API bitmap, free the bits */
100 if (pvBits != NULL &&
101 (psurf->flFlags & BITMAPOBJ_IS_APIBITMAP))
102 {
103 /* Check if we have a DIB section */
104 if (psurf->hSecure)
105 {
106 // FIXME: IMPLEMENT ME!
107 // MmUnsecureVirtualMemory(psurf->hSecure);
108 if (psurf->hDIBSection)
109 {
110 /* DIB was created from a section */
111 NTSTATUS Status;
112
113 pvBits = (PVOID)((ULONG_PTR)pvBits - psurf->dwOffset);
114 Status = ZwUnmapViewOfSection(NtCurrentProcess(), pvBits);
115 if (!NT_SUCCESS(Status))
116 {
117 DPRINT1("Could not unmap section view!\n");
118 // Should we BugCheck here?
119 }
120 }
121 else
122 {
123 /* DIB was allocated */
124 EngFreeUserMem(pvBits);
125 }
126 }
127 else
128 {
129 // FIXME: use TAG
130 ExFreePool(psurf->SurfObj.pvBits);
131 }
132
133 if (psurf->hDIBPalette != NULL)
134 {
135 GreDeleteObject(psurf->hDIBPalette);
136 }
137 }
138
139 return TRUE;
140 }
141
142 /*
143 * @implemented
144 */
145 HBITMAP APIENTRY
146 EngCreateDeviceBitmap(IN DHSURF dhsurf,
147 IN SIZEL Size,
148 IN ULONG Format)
149 {
150 HBITMAP NewBitmap;
151 SURFOBJ *pso;
152
153 NewBitmap = EngCreateBitmap(Size, DIB_GetDIBWidthBytes(Size.cx, BitsPerFormat(Format)), Format, 0, NULL);
154 if (!NewBitmap)
155 {
156 DPRINT1("EngCreateBitmap failed\n");
157 return 0;
158 }
159
160 pso = EngLockSurface((HSURF)NewBitmap);
161 if (!pso)
162 {
163 DPRINT1("EngLockSurface failed on newly created bitmap!\n");
164 GreDeleteObject(NewBitmap);
165 return NULL;
166 }
167
168 pso->dhsurf = dhsurf;
169 EngUnlockSurface(pso);
170
171 return NewBitmap;
172 }
173
174 VOID Decompress4bpp(SIZEL Size, BYTE *CompressedBits, BYTE *UncompressedBits, LONG Delta)
175 {
176 int x = 0;
177 int y = Size.cy - 1;
178 int c;
179 int length;
180 int width = ((Size.cx+1)/2);
181 int height = Size.cy - 1;
182 BYTE *begin = CompressedBits;
183 BYTE *bits = CompressedBits;
184 BYTE *temp;
185 while (y >= 0)
186 {
187 length = *bits++ / 2;
188 if (length)
189 {
190 c = *bits++;
191 while (length--)
192 {
193 if (x >= width) break;
194 temp = UncompressedBits + (((height - y) * Delta) + x);
195 x++;
196 *temp = c;
197 }
198 }
199 else
200 {
201 length = *bits++;
202 switch (length)
203 {
204 case RLE_EOL:
205 x = 0;
206 y--;
207 break;
208 case RLE_END:
209 return;
210 case RLE_DELTA:
211 x += (*bits++)/2;
212 y -= (*bits++)/2;
213 break;
214 default:
215 length /= 2;
216 while (length--)
217 {
218 c = *bits++;
219 if (x < width)
220 {
221 temp = UncompressedBits + (((height - y) * Delta) + x);
222 x++;
223 *temp = c;
224 }
225 }
226 if ((bits - begin) & 1)
227 {
228 bits++;
229 }
230 }
231 }
232 }
233 }
234
235 VOID Decompress8bpp(SIZEL Size, BYTE *CompressedBits, BYTE *UncompressedBits, LONG Delta)
236 {
237 int x = 0;
238 int y = Size.cy - 1;
239 int c;
240 int length;
241 int width = Size.cx;
242 int height = Size.cy - 1;
243 BYTE *begin = CompressedBits;
244 BYTE *bits = CompressedBits;
245 BYTE *temp;
246 while (y >= 0)
247 {
248 length = *bits++;
249 if (length)
250 {
251 c = *bits++;
252 while (length--)
253 {
254 if (x >= width) break;
255 temp = UncompressedBits + (((height - y) * Delta) + x);
256 x++;
257 *temp = c;
258 }
259 }
260 else
261 {
262 length = *bits++;
263 switch (length)
264 {
265 case RLE_EOL:
266 x = 0;
267 y--;
268 break;
269 case RLE_END:
270 return;
271 case RLE_DELTA:
272 x += *bits++;
273 y -= *bits++;
274 break;
275 default:
276 while (length--)
277 {
278 c = *bits++;
279 if (x < width)
280 {
281 temp = UncompressedBits + (((height - y) * Delta) + x);
282 x++;
283 *temp = c;
284 }
285 }
286 if ((bits - begin) & 1)
287 {
288 bits++;
289 }
290 }
291 }
292 }
293 }
294
295 HBITMAP FASTCALL
296 IntCreateBitmap(IN SIZEL Size,
297 IN LONG Width,
298 IN ULONG Format,
299 IN ULONG Flags,
300 IN PVOID Bits)
301 {
302 HBITMAP hbmp;
303 SURFOBJ *pso;
304 PSURFACE psurf;
305 PVOID UncompressedBits;
306 ULONG UncompressedFormat;
307
308 if (Format == 0)
309 return 0;
310
311 psurf = SURFACE_AllocSurfaceWithHandle();
312 if (psurf == NULL)
313 {
314 return 0;
315 }
316 hbmp = psurf->BaseObject.hHmgr;
317
318 pso = &psurf->SurfObj;
319
320 if (Format == BMF_4RLE)
321 {
322 pso->lDelta = DIB_GetDIBWidthBytes(Size.cx, BitsPerFormat(BMF_4BPP));
323 pso->cjBits = pso->lDelta * Size.cy;
324 UncompressedFormat = BMF_4BPP;
325 UncompressedBits = EngAllocMem(FL_ZERO_MEMORY, pso->cjBits, TAG_DIB);
326 Decompress4bpp(Size, (BYTE *)Bits, (BYTE *)UncompressedBits, pso->lDelta);
327 }
328 else if (Format == BMF_8RLE)
329 {
330 pso->lDelta = DIB_GetDIBWidthBytes(Size.cx, BitsPerFormat(BMF_8BPP));
331 pso->cjBits = pso->lDelta * Size.cy;
332 UncompressedFormat = BMF_8BPP;
333 UncompressedBits = EngAllocMem(FL_ZERO_MEMORY, pso->cjBits, TAG_DIB);
334 Decompress8bpp(Size, (BYTE *)Bits, (BYTE *)UncompressedBits, pso->lDelta);
335 }
336 else
337 {
338 pso->lDelta = abs(Width);
339 pso->cjBits = pso->lDelta * Size.cy;
340 UncompressedBits = Bits;
341 UncompressedFormat = Format;
342 }
343
344 if (UncompressedBits != NULL)
345 {
346 pso->pvBits = UncompressedBits;
347 }
348 else
349 {
350 if (pso->cjBits == 0)
351 {
352 pso->pvBits = NULL;
353 }
354 else
355 {
356 if (0 != (Flags & BMF_USERMEM))
357 {
358 pso->pvBits = EngAllocUserMem(pso->cjBits, 0);
359 }
360 else
361 {
362 pso->pvBits = EngAllocMem(0 != (Flags & BMF_NOZEROINIT) ?
363 0 : FL_ZERO_MEMORY,
364 pso->cjBits, TAG_DIB);
365 }
366 if (pso->pvBits == NULL)
367 {
368 SURFACE_UnlockSurface(psurf);
369 SURFACE_FreeSurfaceByHandle(hbmp);
370 SetLastWin32Error(ERROR_NOT_ENOUGH_MEMORY);
371 return 0;
372 }
373 }
374 }
375
376 if (0 == (Flags & BMF_TOPDOWN))
377 {
378 pso->pvScan0 = (PVOID)((ULONG_PTR)pso->pvBits + pso->cjBits - pso->lDelta);
379 pso->lDelta = - pso->lDelta;
380 }
381 else
382 {
383 pso->pvScan0 = pso->pvBits;
384 }
385
386 pso->dhsurf = 0; /* device managed surface */
387 pso->hsurf = (HSURF)hbmp;
388 pso->dhpdev = NULL;
389 pso->hdev = NULL;
390 pso->sizlBitmap = Size;
391 pso->iBitmapFormat = UncompressedFormat;
392 pso->iType = STYPE_BITMAP;
393 pso->fjBitmap = Flags & (BMF_TOPDOWN | BMF_NOZEROINIT);
394 pso->iUniq = 0;
395
396 psurf->flHooks = 0;
397 psurf->flFlags = 0;
398 psurf->dimension.cx = 0;
399 psurf->dimension.cy = 0;
400
401 psurf->hSecure = NULL;
402 psurf->hDIBSection = NULL;
403
404 SURFACE_UnlockSurface(psurf);
405
406 return hbmp;
407 }
408
409 /* Name gleaned from C++ symbol information for SURFMEM::bInitDIB */
410 typedef struct _DEVBITMAPINFO
411 {
412 ULONG Format;
413 ULONG Width;
414 ULONG Height;
415 ULONG Flags;
416 ULONG Size;
417 } DEVBITMAPINFO, *PDEVBITMAPINFO;
418
419 SURFOBJ*
420 FASTCALL
421 SURFMEM_bCreateDib(IN PDEVBITMAPINFO BitmapInfo,
422 IN PVOID Bits)
423 {
424 BOOLEAN Compressed = FALSE;
425 ULONG ScanLine = 0; // Compiler is dumb
426 ULONG Size;
427 SURFOBJ *pso;
428 PSURFACE psurf;
429 SIZEL LocalSize;
430 BOOLEAN AllocatedLocally = FALSE;
431
432 /*
433 * First, check the format so we can get the aligned scanline width.
434 * RLE and the newer fancy-smanshy JPG/PNG support do NOT have scanlines
435 * since they are compressed surfaces!
436 */
437 switch (BitmapInfo->Format)
438 {
439 case BMF_1BPP:
440 ScanLine = ((BitmapInfo->Width + 31) & ~31) >> 3;
441 break;
442
443 case BMF_4BPP:
444 ScanLine = ((BitmapInfo->Width + 7) & ~7) >> 1;
445 break;
446
447 case BMF_8BPP:
448 ScanLine = (BitmapInfo->Width + 3) & ~3;
449 break;
450
451 case BMF_16BPP:
452 ScanLine = ((BitmapInfo->Width + 1) & ~1) << 1;
453 break;
454
455 case BMF_24BPP:
456 ScanLine = ((BitmapInfo->Width * 3) + 3) & ~3;
457 break;
458
459 case BMF_32BPP:
460 ScanLine = BitmapInfo->Width << 2;
461 break;
462
463 case BMF_8RLE:
464 case BMF_4RLE:
465 case BMF_JPEG:
466 case BMF_PNG:
467 Compressed = TRUE;
468 break;
469
470 default:
471 DPRINT1("Invalid bitmap format\n");
472 return NULL;
473 }
474
475 /* Does the device manage its own surface? */
476 if (!Bits)
477 {
478 /* We need to allocate bits for the caller, figure out the size */
479 if (Compressed)
480 {
481 /* Note: we should not be seeing this scenario from ENGDDI */
482 ASSERT(FALSE);
483 Size = BitmapInfo->Size;
484 }
485 else
486 {
487 /* The height times the bytes for each scanline */
488 Size = BitmapInfo->Height * ScanLine;
489 }
490
491 if (Size)
492 {
493 /* Check for allocation flag */
494 if (BitmapInfo->Flags & BMF_USERMEM)
495 {
496 /* Get the bits from user-mode memory */
497 Bits = EngAllocUserMem(Size, 'mbuG');
498 }
499 else
500 {
501 /* Get kernel bits (zeroed out if requested) */
502 Bits = EngAllocMem((BitmapInfo->Flags & BMF_NOZEROINIT) ? 0 : FL_ZERO_MEMORY,
503 Size,
504 TAG_DIB);
505 }
506 AllocatedLocally = TRUE;
507 /* Bail out if that failed */
508 if (!Bits) return NULL;
509 }
510 }
511 else
512 {
513 /* Should not have asked for user memory */
514 ASSERT((BitmapInfo->Flags & BMF_USERMEM) == 0);
515 }
516
517 /* Allocate the actual surface object structure */
518 psurf = SURFACE_AllocSurfaceWithHandle();
519 if (!psurf)
520 {
521 if(Bits && AllocatedLocally)
522 {
523 if(BitmapInfo->Flags & BMF_USERMEM)
524 EngFreeUserMem(Bits);
525 else
526 EngFreeMem(Bits);
527 }
528 return NULL;
529 }
530
531 /* We should now have our surface object */
532 pso = &psurf->SurfObj;
533
534 /* Save format and flags */
535 pso->iBitmapFormat = BitmapInfo->Format;
536 pso->fjBitmap = BitmapInfo->Flags & (BMF_TOPDOWN | BMF_UMPDMEM | BMF_USERMEM);
537
538 /* Save size and type */
539 LocalSize.cy = BitmapInfo->Height;
540 LocalSize.cx = BitmapInfo->Width;
541 pso->sizlBitmap = LocalSize;
542 pso->iType = STYPE_BITMAP;
543
544 /* Device-managed surface, no flags or dimension */
545 pso->dhsurf = 0;
546 pso->dhpdev = NULL;
547 pso->hdev = NULL;
548 psurf->flFlags = 0;
549 psurf->dimension.cx = 0;
550 psurf->dimension.cy = 0;
551 psurf->hSecure = NULL;
552 psurf->hDIBSection = NULL;
553 psurf->flHooks = 0;
554
555 /* Set bits */
556 pso->pvBits = Bits;
557
558 /* Check for bitmap type */
559 if (!Compressed)
560 {
561 /* Number of bits is based on the height times the scanline */
562 pso->cjBits = BitmapInfo->Height * ScanLine;
563 if (BitmapInfo->Flags & BMF_TOPDOWN)
564 {
565 /* For topdown, the base address starts with the bits */
566 pso->pvScan0 = pso->pvBits;
567 pso->lDelta = ScanLine;
568 }
569 else
570 {
571 /* Otherwise we start with the end and go up */
572 pso->pvScan0 = (PVOID)((ULONG_PTR)pso->pvBits + pso->cjBits - ScanLine);
573 pso->lDelta = -ScanLine;
574 }
575 }
576 else
577 {
578 /* Compressed surfaces don't have scanlines! */
579 pso->lDelta = 0;
580 pso->cjBits = BitmapInfo->Size;
581
582 /* Check for JPG or PNG */
583 if ((BitmapInfo->Format != BMF_JPEG) && (BitmapInfo->Format != BMF_PNG))
584 {
585 /* Wherever the bit data is */
586 pso->pvScan0 = pso->pvBits;
587 }
588 else
589 {
590 /* Fancy formats don't use a base address */
591 pso->pvScan0 = NULL;
592 ASSERT(FALSE); // ENGDDI shouldn't be creating PNGs for drivers ;-)
593 }
594 }
595
596 /* Finally set the handle and uniq */
597 pso->hsurf = (HSURF)psurf->BaseObject.hHmgr;
598 pso->iUniq = 0;
599
600 /* Unlock and return the surface */
601 SURFACE_UnlockSurface(psurf);
602 return pso;
603 }
604
605 /*
606 * @implemented
607 */
608 HBITMAP
609 APIENTRY
610 EngCreateBitmap(IN SIZEL Size,
611 IN LONG Width,
612 IN ULONG Format,
613 IN ULONG Flags,
614 IN PVOID Bits)
615 {
616 SURFOBJ* Surface;
617 DEVBITMAPINFO BitmapInfo;
618
619 /* Capture the parameters */
620 BitmapInfo.Format = Format;
621 BitmapInfo.Width = Size.cx;
622 BitmapInfo.Height = Size.cy;
623 BitmapInfo.Flags = Flags;
624
625 /*
626 * If the display driver supports framebuffer access, use the scanline width
627 * to determine the actual width of the bitmap, and convert it to pels instead
628 * of bytes.
629 */
630 if ((Bits) && (Width))
631 {
632 switch (BitmapInfo.Format)
633 {
634 /* Do the conversion for each bit depth we support */
635 case BMF_1BPP:
636 BitmapInfo.Width = Width * 8;
637 break;
638 case BMF_4BPP:
639 BitmapInfo.Width = Width * 2;
640 break;
641 case BMF_8BPP:
642 BitmapInfo.Width = Width;
643 break;
644 case BMF_16BPP:
645 BitmapInfo.Width = Width / 2;
646 break;
647 case BMF_24BPP:
648 BitmapInfo.Width = Width / 3;
649 break;
650 case BMF_32BPP:
651 BitmapInfo.Width = Width / 4;
652 break;
653 }
654 }
655
656 /* Now create the surface */
657 Surface = SURFMEM_bCreateDib(&BitmapInfo, Bits);
658 if (!Surface) return 0;
659
660 /* Set public ownership and reutrn the handle */
661 GDIOBJ_SetOwnership(Surface->hsurf, NULL);
662 return Surface->hsurf;
663 }
664
665 /*
666 * @unimplemented
667 */
668 HSURF APIENTRY
669 EngCreateDeviceSurface(IN DHSURF dhsurf,
670 IN SIZEL Size,
671 IN ULONG Format)
672 {
673 HSURF hsurf;
674 SURFOBJ *pso;
675 PSURFACE psurf;
676
677 psurf = SURFACE_AllocSurfaceWithHandle();
678 if (!psurf)
679 {
680 return 0;
681 }
682
683 hsurf = psurf->BaseObject.hHmgr;
684 GDIOBJ_SetOwnership(hsurf, NULL);
685
686 pso = &psurf->SurfObj;
687
688 pso->dhsurf = dhsurf;
689 pso->hsurf = hsurf;
690 pso->sizlBitmap = Size;
691 pso->iBitmapFormat = Format;
692 pso->lDelta = DIB_GetDIBWidthBytes(Size.cx, BitsPerFormat(Format));
693 pso->iType = STYPE_DEVICE;
694 pso->iUniq = 0;
695
696 psurf->flHooks = 0;
697
698 SURFACE_UnlockSurface(psurf);
699
700 return hsurf;
701 }
702
703 /*
704 * @implemented
705 */
706 BOOL
707 APIENTRY
708 EngAssociateSurface(
709 IN HSURF hsurf,
710 IN HDEV hdev,
711 IN FLONG flHooks)
712 {
713 SURFOBJ *pso;
714 PSURFACE psurf;
715 PDEVOBJ* ppdev;
716
717 ppdev = (PDEVOBJ*)hdev;
718
719 /* Lock the surface */
720 psurf = SURFACE_LockSurface(hsurf);
721 if (!psurf)
722 {
723 return FALSE;
724 }
725 pso = &psurf->SurfObj;
726
727 /* Associate the hdev */
728 pso->hdev = hdev;
729 pso->dhpdev = ppdev->dhpdev;
730
731 /* Hook up specified functions */
732 psurf->flHooks = flHooks;
733
734 SURFACE_UnlockSurface(psurf);
735
736 return TRUE;
737 }
738
739 /*
740 * @implemented
741 */
742 BOOL APIENTRY
743 EngModifySurface(
744 IN HSURF hsurf,
745 IN HDEV hdev,
746 IN FLONG flHooks,
747 IN FLONG flSurface,
748 IN DHSURF dhsurf,
749 OUT VOID *pvScan0,
750 IN LONG lDelta,
751 IN VOID *pvReserved)
752 {
753 SURFOBJ *pso;
754 PSURFACE psurf;
755 PDEVOBJ* ppdev;
756
757 psurf = SURFACE_LockSurface(hsurf);
758 if (psurf == NULL)
759 {
760 return FALSE;
761 }
762
763 ppdev = (PDEVOBJ*)hdev;
764 pso = &psurf->SurfObj;
765 pso->dhsurf = dhsurf;
766 pso->lDelta = lDelta;
767 pso->pvScan0 = pvScan0;
768
769 /* Associate the hdev */
770 pso->hdev = hdev;
771 pso->dhpdev = ppdev->dhpdev;
772
773 /* Hook up specified functions */
774 psurf->flHooks = flHooks;
775
776 SURFACE_UnlockSurface(psurf);
777
778 return TRUE;
779 }
780
781 /*
782 * @implemented
783 */
784 BOOL APIENTRY
785 EngDeleteSurface(IN HSURF hsurf)
786 {
787 GDIOBJ_SetOwnership(hsurf, PsGetCurrentProcess());
788 SURFACE_FreeSurfaceByHandle(hsurf);
789 return TRUE;
790 }
791
792 /*
793 * @implemented
794 */
795 BOOL APIENTRY
796 EngEraseSurface(SURFOBJ *pso,
797 RECTL *Rect,
798 ULONG iColor)
799 {
800 ASSERT(pso);
801 ASSERT(Rect);
802 return FillSolid(pso, Rect, iColor);
803 }
804
805 /*
806 * @implemented
807 */
808 SURFOBJ * APIENTRY
809 NtGdiEngLockSurface(IN HSURF hsurf)
810 {
811 return EngLockSurface(hsurf);
812 }
813
814
815 /*
816 * @implemented
817 */
818 SURFOBJ * APIENTRY
819 EngLockSurface(IN HSURF hsurf)
820 {
821 SURFACE *psurf = GDIOBJ_ShareLockObj(hsurf, GDI_OBJECT_TYPE_BITMAP);
822
823 if (psurf != NULL)
824 return &psurf->SurfObj;
825
826 return NULL;
827 }
828
829
830 /*
831 * @implemented
832 */
833 VOID APIENTRY
834 NtGdiEngUnlockSurface(IN SURFOBJ *pso)
835 {
836 EngUnlockSurface(pso);
837 }
838
839 /*
840 * @implemented
841 */
842 VOID APIENTRY
843 EngUnlockSurface(IN SURFOBJ *pso)
844 {
845 if (pso != NULL)
846 {
847 SURFACE *psurf = CONTAINING_RECORD(pso, SURFACE, SurfObj);
848 GDIOBJ_ShareUnlockObjByPtr((POBJ)psurf);
849 }
850 }
851
852
853 /* EOF */