1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 // MARKER(update_precomp.py): autogen include statement, do not remove 25 #include "precompiled_vcl.hxx" 26 27 #include <tools/svwin.h> 28 29 #include <vcl/bitmap.hxx> // for BitmapSystemData 30 #include <vcl/salbtype.hxx> 31 32 #include <win/wincomp.hxx> 33 #include <win/salgdi.h> 34 #include <win/saldata.hxx> 35 #include <win/salbmp.h> 36 37 #include <string.h> 38 39 // ----------- 40 // - Inlines - 41 // ----------- 42 43 inline void ImplSetPixel4( const HPBYTE pScanline, long nX, const BYTE cIndex ) 44 { 45 BYTE& rByte = pScanline[ nX >> 1 ]; 46 47 ( nX & 1 ) ? ( rByte &= 0xf0, rByte |= ( cIndex & 0x0f ) ) : 48 ( rByte &= 0x0f, rByte |= ( cIndex << 4 ) ); 49 } 50 51 // ---------------- 52 // - WinSalBitmap - 53 // ---------------- 54 55 WinSalBitmap::WinSalBitmap() : 56 mhDIB ( 0 ), 57 mhDDB ( 0 ), 58 mnBitCount ( 0 ) 59 { 60 } 61 62 // ------------------------------------------------------------------ 63 64 WinSalBitmap::~WinSalBitmap() 65 { 66 Destroy(); 67 } 68 69 // ------------------------------------------------------------------ 70 71 bool WinSalBitmap::Create( HANDLE hBitmap, bool bDIB, bool bCopyHandle ) 72 { 73 bool bRet = TRUE; 74 75 if( bDIB ) 76 mhDIB = (HGLOBAL) ( bCopyHandle ? ImplCopyDIBOrDDB( hBitmap, TRUE ) : hBitmap ); 77 else 78 mhDDB = (HBITMAP) ( bCopyHandle ? ImplCopyDIBOrDDB( hBitmap, FALSE ) : hBitmap ); 79 80 if( mhDIB ) 81 { 82 PBITMAPINFOHEADER pBIH = (PBITMAPINFOHEADER) GlobalLock( mhDIB ); 83 84 maSize = Size( pBIH->biWidth, pBIH->biHeight ); 85 mnBitCount = pBIH->biBitCount; 86 87 if( mnBitCount ) 88 mnBitCount = ( mnBitCount <= 1 ) ? 1 : ( mnBitCount <= 4 ) ? 4 : ( mnBitCount <= 8 ) ? 8 : 24; 89 90 GlobalUnlock( mhDIB ); 91 } 92 else if( mhDDB ) 93 { 94 BITMAP aDDBInfo; 95 96 if( WIN_GetObject( mhDDB, sizeof( BITMAP ), &aDDBInfo ) ) 97 { 98 maSize = Size( aDDBInfo.bmWidth, aDDBInfo.bmHeight ); 99 mnBitCount = aDDBInfo.bmPlanes * aDDBInfo.bmBitsPixel; 100 101 if( mnBitCount ) 102 { 103 mnBitCount = ( mnBitCount <= 1 ) ? 1 : 104 ( mnBitCount <= 4 ) ? 4 : 105 ( mnBitCount <= 8 ) ? 8 : 24; 106 } 107 } 108 else 109 { 110 mhDDB = 0; 111 bRet = FALSE; 112 } 113 } 114 else 115 bRet = FALSE; 116 117 return bRet; 118 } 119 120 // ------------------------------------------------------------------ 121 122 bool WinSalBitmap::Create( const Size& rSize, sal_uInt16 nBitCount, const BitmapPalette& rPal ) 123 { 124 bool bRet = FALSE; 125 126 mhDIB = ImplCreateDIB( rSize, nBitCount, rPal ); 127 128 if( mhDIB ) 129 { 130 maSize = rSize; 131 mnBitCount = nBitCount; 132 bRet = TRUE; 133 } 134 135 return bRet; 136 } 137 138 // ------------------------------------------------------------------ 139 140 bool WinSalBitmap::Create( const SalBitmap& rSSalBitmap ) 141 { 142 bool bRet = FALSE; 143 const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap); 144 145 if ( rSalBitmap.mhDIB || rSalBitmap.mhDDB ) 146 { 147 HANDLE hNewHdl = ImplCopyDIBOrDDB( rSalBitmap.mhDIB ? rSalBitmap.mhDIB : rSalBitmap.mhDDB, 148 rSalBitmap.mhDIB != 0 ); 149 150 if ( hNewHdl ) 151 { 152 if( rSalBitmap.mhDIB ) 153 mhDIB = (HGLOBAL) hNewHdl; 154 else if( rSalBitmap.mhDDB ) 155 mhDDB = (HBITMAP) hNewHdl; 156 157 maSize = rSalBitmap.maSize; 158 mnBitCount = rSalBitmap.mnBitCount; 159 160 bRet = TRUE; 161 } 162 } 163 164 return bRet; 165 } 166 167 // ------------------------------------------------------------------ 168 169 bool WinSalBitmap::Create( const SalBitmap& rSSalBmp, SalGraphics* pSGraphics ) 170 { 171 bool bRet = FALSE; 172 173 const WinSalBitmap& rSalBmp = static_cast<const WinSalBitmap&>(rSSalBmp); 174 WinSalGraphics* pGraphics = static_cast<WinSalGraphics*>(pSGraphics); 175 176 if( rSalBmp.mhDIB ) 177 { 178 PBITMAPINFO pBI = (PBITMAPINFO) GlobalLock( rSalBmp.mhDIB ); 179 PBITMAPINFOHEADER pBIH = (PBITMAPINFOHEADER) pBI; 180 HDC hDC = pGraphics->mhDC; 181 HBITMAP hNewDDB; 182 BITMAP aDDBInfo; 183 PBYTE pBits = (PBYTE) pBI + *(DWORD*) pBI + 184 ImplGetDIBColorCount( rSalBmp.mhDIB ) * sizeof( RGBQUAD ); 185 186 if( pBIH->biBitCount == 1 ) 187 { 188 hNewDDB = CreateBitmap( pBIH->biWidth, pBIH->biHeight, 1, 1, NULL ); 189 190 if( hNewDDB ) 191 SetDIBits( hDC, hNewDDB, 0, pBIH->biHeight, pBits, pBI, DIB_RGB_COLORS ); 192 } 193 else 194 hNewDDB = CreateDIBitmap( hDC, (PBITMAPINFOHEADER) pBI, CBM_INIT, pBits, pBI, DIB_RGB_COLORS ); 195 196 GlobalUnlock( rSalBmp.mhDIB ); 197 198 if( hNewDDB && WIN_GetObject( hNewDDB, sizeof( BITMAP ), &aDDBInfo ) ) 199 { 200 mhDDB = hNewDDB; 201 maSize = Size( aDDBInfo.bmWidth, aDDBInfo.bmHeight ); 202 mnBitCount = aDDBInfo.bmPlanes * aDDBInfo.bmBitsPixel; 203 204 bRet = TRUE; 205 } 206 else if( hNewDDB ) 207 DeleteObject( hNewDDB ); 208 } 209 210 return bRet; 211 } 212 213 // ------------------------------------------------------------------ 214 215 bool WinSalBitmap::Create( const SalBitmap& rSSalBmp, sal_uInt16 nNewBitCount ) 216 { 217 bool bRet = FALSE; 218 219 const WinSalBitmap& rSalBmp = static_cast<const WinSalBitmap&>(rSSalBmp); 220 221 if( rSalBmp.mhDDB ) 222 { 223 mhDIB = ImplCreateDIB( rSalBmp.maSize, nNewBitCount, BitmapPalette() ); 224 225 if( mhDIB ) 226 { 227 PBITMAPINFO pBI = (PBITMAPINFO) GlobalLock( mhDIB ); 228 const int nLines = (int) rSalBmp.maSize.Height(); 229 HDC hDC = GetDC( 0 ); 230 PBYTE pBits = (PBYTE) pBI + *(DWORD*) pBI + 231 ImplGetDIBColorCount( mhDIB ) * sizeof( RGBQUAD ); 232 SalData* pSalData = GetSalData(); 233 HPALETTE hOldPal = 0; 234 235 if ( pSalData->mhDitherPal ) 236 { 237 hOldPal = SelectPalette( hDC, pSalData->mhDitherPal, TRUE ); 238 RealizePalette( hDC ); 239 } 240 241 if( GetDIBits( hDC, rSalBmp.mhDDB, 0, nLines, pBits, pBI, DIB_RGB_COLORS ) == nLines ) 242 { 243 GlobalUnlock( mhDIB ); 244 maSize = rSalBmp.maSize; 245 mnBitCount = nNewBitCount; 246 bRet = TRUE; 247 } 248 else 249 { 250 GlobalUnlock( mhDIB ); 251 GlobalFree( mhDIB ); 252 mhDIB = 0; 253 } 254 255 if( hOldPal ) 256 SelectPalette( hDC, hOldPal, TRUE ); 257 258 ReleaseDC( 0, hDC ); 259 } 260 } 261 262 return bRet; 263 } 264 265 // ------------------------------------------------------------------ 266 267 void WinSalBitmap::Destroy() 268 { 269 if( mhDIB ) 270 GlobalFree( mhDIB ); 271 else if( mhDDB ) 272 DeleteObject( mhDDB ); 273 274 maSize = Size(); 275 mnBitCount = 0; 276 } 277 278 // ------------------------------------------------------------------ 279 280 sal_uInt16 WinSalBitmap::ImplGetDIBColorCount( HGLOBAL hDIB ) 281 { 282 sal_uInt16 nColors = 0; 283 284 if( hDIB ) 285 { 286 PBITMAPINFO pBI = (PBITMAPINFO) GlobalLock( hDIB ); 287 PBITMAPINFOHEADER pBIH = (PBITMAPINFOHEADER) pBI; 288 289 if ( pBIH->biSize != sizeof( BITMAPCOREHEADER ) ) 290 { 291 if( pBIH->biBitCount <= 8 ) 292 { 293 if ( pBIH->biClrUsed ) 294 nColors = (sal_uInt16) pBIH->biClrUsed; 295 else 296 nColors = 1 << pBIH->biBitCount; 297 } 298 } 299 else if( ( (PBITMAPCOREHEADER) pBI )->bcBitCount <= 8 ) 300 nColors = 1 << ( (PBITMAPCOREHEADER) pBI )->bcBitCount; 301 302 GlobalUnlock( hDIB ); 303 } 304 305 return nColors; 306 } 307 308 // ------------------------------------------------------------------ 309 310 HGLOBAL WinSalBitmap::ImplCreateDIB( const Size& rSize, sal_uInt16 nBits, const BitmapPalette& rPal ) 311 { 312 DBG_ASSERT( nBits == 1 || nBits == 4 || nBits == 8 || nBits == 16 || nBits == 24, "Unsupported BitCount!" ); 313 314 HGLOBAL hDIB = 0; 315 316 if ( rSize.Width() && rSize.Height() ) 317 { 318 const sal_uLong nImageSize = AlignedWidth4Bytes( nBits * rSize.Width() ) * rSize.Height(); 319 const sal_uInt16 nColors = ( nBits <= 8 ) ? ( 1 << nBits ) : 0; 320 321 hDIB = GlobalAlloc( GHND, sizeof( BITMAPINFOHEADER ) + nColors * sizeof( RGBQUAD ) + nImageSize ); 322 323 if( hDIB ) 324 { 325 PBITMAPINFO pBI = (PBITMAPINFO) GlobalLock( hDIB ); 326 PBITMAPINFOHEADER pBIH = (PBITMAPINFOHEADER) pBI; 327 328 pBIH->biSize = sizeof( BITMAPINFOHEADER ); 329 pBIH->biWidth = rSize.Width(); 330 pBIH->biHeight = rSize.Height(); 331 pBIH->biPlanes = 1; 332 pBIH->biBitCount = nBits; 333 pBIH->biCompression = BI_RGB; 334 pBIH->biSizeImage = nImageSize; 335 pBIH->biXPelsPerMeter = 0; 336 pBIH->biYPelsPerMeter = 0; 337 pBIH->biClrUsed = 0; 338 pBIH->biClrImportant = 0; 339 340 if ( nColors ) 341 { 342 const sal_uInt16 nMinCount = Min( nColors, rPal.GetEntryCount() ); 343 344 if( nMinCount ) 345 memcpy( pBI->bmiColors, rPal.ImplGetColorBuffer(), nMinCount * sizeof( RGBQUAD ) ); 346 } 347 348 GlobalUnlock( hDIB ); 349 } 350 } 351 352 return hDIB; 353 } 354 355 // ------------------------------------------------------------------ 356 357 HANDLE WinSalBitmap::ImplCopyDIBOrDDB( HANDLE hHdl, bool bDIB ) 358 { 359 HANDLE hCopy = 0; 360 361 if ( bDIB && hHdl ) 362 { 363 const sal_uLong nSize = GlobalSize( hHdl ); 364 365 if ( (hCopy = GlobalAlloc( GHND, nSize )) != 0 ) 366 { 367 memcpy( (LPSTR) GlobalLock( hCopy ), (LPSTR) GlobalLock( hHdl ), nSize ); 368 369 GlobalUnlock( hCopy ); 370 GlobalUnlock( hHdl ); 371 } 372 } 373 else if ( hHdl ) 374 { 375 BITMAP aBmp; 376 377 // Source-Bitmap nach Groesse befragen 378 WIN_GetObject( hHdl, sizeof( BITMAP ), (LPSTR) &aBmp ); 379 380 // Destination-Bitmap erzeugen 381 if ( (hCopy = CreateBitmapIndirect( &aBmp )) != 0 ) 382 { 383 HDC hBmpDC = CreateCompatibleDC( 0 ); 384 HBITMAP hBmpOld = (HBITMAP) SelectObject( hBmpDC, hHdl ); 385 HDC hCopyDC = CreateCompatibleDC( hBmpDC ); 386 HBITMAP hCopyOld = (HBITMAP) SelectObject( hCopyDC, hCopy ); 387 388 BitBlt( hCopyDC, 0, 0, aBmp.bmWidth, aBmp.bmHeight, hBmpDC, 0, 0, SRCCOPY ); 389 390 SelectObject( hCopyDC, hCopyOld ); 391 DeleteDC( hCopyDC ); 392 393 SelectObject( hBmpDC, hBmpOld ); 394 DeleteDC( hBmpDC ); 395 } 396 } 397 398 return hCopy; 399 } 400 401 // ------------------------------------------------------------------ 402 403 BitmapBuffer* WinSalBitmap::AcquireBuffer( bool /*bReadOnly*/ ) 404 { 405 BitmapBuffer* pBuffer = NULL; 406 407 if( mhDIB ) 408 { 409 PBITMAPINFO pBI = (PBITMAPINFO) GlobalLock( mhDIB ); 410 PBITMAPINFOHEADER pBIH = (PBITMAPINFOHEADER) pBI; 411 412 if( ( pBIH->biCompression == BI_RLE4 ) || ( pBIH->biCompression == BI_RLE8 ) ) 413 { 414 Size aSizePix( pBIH->biWidth, pBIH->biHeight ); 415 HGLOBAL hNewDIB = ImplCreateDIB( aSizePix, pBIH->biBitCount, BitmapPalette() ); 416 417 if( hNewDIB ) 418 { 419 PBITMAPINFO pNewBI = (PBITMAPINFO) GlobalLock( hNewDIB ); 420 PBITMAPINFOHEADER pNewBIH = (PBITMAPINFOHEADER) pNewBI; 421 const sal_uInt16 nColorCount = ImplGetDIBColorCount( hNewDIB ); 422 const sal_uLong nOffset = *(DWORD*) pBI + nColorCount * sizeof( RGBQUAD ); 423 BYTE* pOldBits = (PBYTE) pBI + nOffset; 424 BYTE* pNewBits = (PBYTE) pNewBI + nOffset; 425 426 memcpy( pNewBI, pBI, nOffset ); 427 pNewBIH->biCompression = 0; 428 ImplDecodeRLEBuffer( pOldBits, pNewBits, aSizePix, pBIH->biCompression == BI_RLE4 ); 429 430 GlobalUnlock( mhDIB ); 431 GlobalFree( mhDIB ); 432 mhDIB = hNewDIB; 433 pBI = pNewBI; 434 pBIH = pNewBIH; 435 } 436 } 437 438 if( pBIH->biPlanes == 1 ) 439 { 440 pBuffer = new BitmapBuffer; 441 442 pBuffer->mnFormat = BMP_FORMAT_BOTTOM_UP | 443 ( pBIH->biBitCount == 1 ? BMP_FORMAT_1BIT_MSB_PAL : 444 pBIH->biBitCount == 4 ? BMP_FORMAT_4BIT_MSN_PAL : 445 pBIH->biBitCount == 8 ? BMP_FORMAT_8BIT_PAL : 446 pBIH->biBitCount == 16 ? BMP_FORMAT_16BIT_TC_LSB_MASK : 447 pBIH->biBitCount == 24 ? BMP_FORMAT_24BIT_TC_BGR : 448 pBIH->biBitCount == 32 ? BMP_FORMAT_32BIT_TC_MASK : 0UL ); 449 450 if( BMP_SCANLINE_FORMAT( pBuffer->mnFormat ) ) 451 { 452 pBuffer->mnWidth = maSize.Width(); 453 pBuffer->mnHeight = maSize.Height(); 454 pBuffer->mnScanlineSize = AlignedWidth4Bytes( maSize.Width() * pBIH->biBitCount ); 455 pBuffer->mnBitCount = (sal_uInt16) pBIH->biBitCount; 456 457 if( pBuffer->mnBitCount <= 8 ) 458 { 459 const sal_uInt16 nPalCount = ImplGetDIBColorCount( mhDIB ); 460 461 pBuffer->maPalette.SetEntryCount( nPalCount ); 462 memcpy( pBuffer->maPalette.ImplGetColorBuffer(), pBI->bmiColors, nPalCount * sizeof( RGBQUAD ) ); 463 pBuffer->mpBits = (PBYTE) pBI + *(DWORD*) pBI + nPalCount * sizeof( RGBQUAD ); 464 } 465 else if( ( pBIH->biBitCount == 16 ) || ( pBIH->biBitCount == 32 ) ) 466 { 467 sal_uLong nOffset = 0UL; 468 469 if( pBIH->biCompression == BI_BITFIELDS ) 470 { 471 nOffset = 3 * sizeof( RGBQUAD ); 472 pBuffer->maColorMask = ColorMask( *(UINT32*) &pBI->bmiColors[ 0 ], 473 *(UINT32*) &pBI->bmiColors[ 1 ], 474 *(UINT32*) &pBI->bmiColors[ 2 ] ); 475 } 476 else if( pBIH->biBitCount == 16 ) 477 pBuffer->maColorMask = ColorMask( 0x00007c00UL, 0x000003e0UL, 0x0000001fUL ); 478 else 479 pBuffer->maColorMask = ColorMask( 0x00ff0000UL, 0x0000ff00UL, 0x000000ffUL ); 480 481 pBuffer->mpBits = (PBYTE) pBI + *(DWORD*) pBI + nOffset; 482 } 483 else 484 pBuffer->mpBits = (PBYTE) pBI + *(DWORD*) pBI; 485 } 486 else 487 { 488 GlobalUnlock( mhDIB ); 489 delete pBuffer; 490 pBuffer = NULL; 491 } 492 } 493 else 494 GlobalUnlock( mhDIB ); 495 } 496 497 return pBuffer; 498 } 499 500 // ------------------------------------------------------------------ 501 502 void WinSalBitmap::ReleaseBuffer( BitmapBuffer* pBuffer, bool bReadOnly ) 503 { 504 if( pBuffer ) 505 { 506 if( mhDIB ) 507 { 508 if( !bReadOnly && !!pBuffer->maPalette ) 509 { 510 PBITMAPINFO pBI = (PBITMAPINFO) GlobalLock( mhDIB ); 511 const sal_uInt16 nCount = pBuffer->maPalette.GetEntryCount(); 512 const sal_uInt16 nDIBColorCount = ImplGetDIBColorCount( mhDIB ); 513 memcpy( pBI->bmiColors, pBuffer->maPalette.ImplGetColorBuffer(), Min( nDIBColorCount, nCount ) * sizeof( RGBQUAD ) ); 514 GlobalUnlock( mhDIB ); 515 } 516 517 GlobalUnlock( mhDIB ); 518 } 519 520 delete pBuffer; 521 } 522 } 523 524 // ------------------------------------------------------------------ 525 526 void WinSalBitmap::ImplDecodeRLEBuffer( const BYTE* pSrcBuf, BYTE* pDstBuf, 527 const Size& rSizePixel, bool bRLE4 ) 528 { 529 HPBYTE pRLE = (HPBYTE) pSrcBuf; 530 HPBYTE pDIB = (HPBYTE) pDstBuf; 531 HPBYTE pRow = (HPBYTE) pDstBuf; 532 sal_uLong nWidthAl = AlignedWidth4Bytes( rSizePixel.Width() * ( bRLE4 ? 4UL : 8UL ) ); 533 HPBYTE pLast = pDIB + rSizePixel.Height() * nWidthAl - 1; 534 sal_uLong nCountByte; 535 sal_uLong nRunByte; 536 sal_uLong nX = 0; 537 sal_uLong i; 538 BYTE cTmp; 539 bool bEndDecoding = FALSE; 540 541 if( pRLE && pDIB ) 542 { 543 do 544 { 545 if( ( nCountByte = *pRLE++ ) == 0 ) 546 { 547 nRunByte = *pRLE++; 548 549 if( nRunByte > 2UL ) 550 { 551 if( bRLE4 ) 552 { 553 nCountByte = nRunByte >> 1UL; 554 555 for( i = 0; i < nCountByte; i++ ) 556 { 557 cTmp = *pRLE++; 558 ImplSetPixel4( pDIB, nX++, cTmp >> 4 ); 559 ImplSetPixel4( pDIB, nX++, cTmp & 0x0f ); 560 } 561 562 if( nRunByte & 1 ) 563 ImplSetPixel4( pDIB, nX++, *pRLE++ >> 4 ); 564 565 if( ( ( nRunByte + 1 ) >> 1 ) & 1 ) 566 pRLE++; 567 } 568 else 569 { 570 memcpy( &pDIB[ nX ], pRLE, nRunByte ); 571 pRLE += nRunByte; 572 nX += nRunByte; 573 574 if( nRunByte & 1 ) 575 pRLE++; 576 } 577 } 578 else if( !nRunByte ) 579 { 580 pDIB = ( pRow += nWidthAl ); 581 nX = 0UL; 582 } 583 else if( nRunByte == 1 ) 584 bEndDecoding = TRUE; 585 else 586 { 587 nX += *pRLE++; 588 pDIB = ( pRow += ( *pRLE++ ) * nWidthAl ); 589 } 590 } 591 else 592 { 593 cTmp = *pRLE++; 594 595 if( bRLE4 ) 596 { 597 nRunByte = nCountByte >> 1; 598 599 for( i = 0; i < nRunByte; i++ ) 600 { 601 ImplSetPixel4( pDIB, nX++, cTmp >> 4 ); 602 ImplSetPixel4( pDIB, nX++, cTmp & 0x0f ); 603 } 604 605 if( nCountByte & 1 ) 606 ImplSetPixel4( pDIB, nX++, cTmp >> 4 ); 607 } 608 else 609 { 610 for( i = 0; i < nCountByte; i++ ) 611 pDIB[ nX++ ] = cTmp; 612 } 613 } 614 } 615 while( !bEndDecoding && ( pDIB <= pLast ) ); 616 } 617 } 618 619 bool WinSalBitmap::GetSystemData( BitmapSystemData& rData ) 620 { 621 bool bRet = false; 622 if( mhDIB || mhDDB ) 623 { 624 bRet = true; 625 rData.pDIB = mhDIB; 626 rData.pDDB = mhDDB; 627 const Size& rSize = GetSize (); 628 rData.mnWidth = rSize.Width(); 629 rData.mnHeight = rSize.Height(); 630 } 631 return bRet; 632 } 633