]> git.saurik.com Git - wxWidgets.git/blame_incremental - src/os2/bitmap.cpp
correction to maintain data array in synch with string array
[wxWidgets.git] / src / os2 / bitmap.cpp
... / ...
CommitLineData
1/////////////////////////////////////////////////////////////////////////////
2// Name: bitmap.cpp
3// Purpose: wxBitmap
4// Author: David Webster
5// Modified by:
6// Created: 08/08/99
7// RCS-ID: $Id$
8// Copyright: (c) David Webster
9// Licence: wxWindows licence
10/////////////////////////////////////////////////////////////////////////////
11
12#ifdef __GNUG__
13 #pragma implementation "bitmap.h"
14#endif
15
16// For compilers that support precompilation, includes "wx.h".
17#include "wx/wxprec.h"
18
19#ifndef WX_PRECOMP
20 #include <stdio.h>
21
22 #include "wx/list.h"
23 #include "wx/utils.h"
24 #include "wx/app.h"
25 #include "wx/palette.h"
26 #include "wx/dcmemory.h"
27 #include "wx/bitmap.h"
28 #include "wx/icon.h"
29#endif
30
31#include "wx/os2/private.h"
32#include "wx/log.h"
33
34//#include "wx/msw/dib.h"
35#include "wx/image.h"
36
37// ----------------------------------------------------------------------------
38// macros
39// ----------------------------------------------------------------------------
40
41IMPLEMENT_DYNAMIC_CLASS(wxBitmap, wxGDIObject)
42IMPLEMENT_DYNAMIC_CLASS(wxMask, wxObject)
43
44IMPLEMENT_DYNAMIC_CLASS(wxBitmapHandler, wxObject)
45
46// ============================================================================
47// implementation
48// ============================================================================
49
50// ----------------------------------------------------------------------------
51// wxBitmapRefData
52// ----------------------------------------------------------------------------
53
54wxBitmapRefData::wxBitmapRefData()
55{
56 m_nQuality = 0;
57 m_pSelectedInto = NULL;
58 m_nNumColors = 0;
59 m_pBitmapMask = NULL;
60 m_hBitmap = (WXHBITMAP) NULL;
61} // end of wxBitmapRefData::wxBitmapRefData
62
63void wxBitmapRefData::Free()
64{
65 wxASSERT_MSG( !m_pSelectedInto,
66 wxT("deleting bitmap still selected into wxMemoryDC") );
67
68 if (m_hBitmap)
69 {
70 if ( !::GpiDeleteBitmap((HBITMAP)m_hBitmap) )
71 {
72 wxLogLastError("GpiDeleteBitmap(hbitmap)");
73 }
74 }
75
76 delete m_pBitmapMask;
77 m_pBitmapMask = NULL;
78} // end of wxBitmapRefData::Free
79
80// ----------------------------------------------------------------------------
81// wxBitmap creation
82// ----------------------------------------------------------------------------
83
84// this function should be called from all wxBitmap ctors
85void wxBitmap::Init()
86{
87 // m_refData = NULL; done in the base class ctor
88
89 if (wxTheBitmapList)
90 wxTheBitmapList->AddBitmap(this);
91}
92
93bool wxBitmap::CopyFromIconOrCursor(
94 const wxGDIImage& rIcon
95)
96{
97 wxBitmapRefData* pRefData = new wxBitmapRefData;
98
99 m_refData = pRefData;
100
101 pRefData->m_nWidth = rIcon.GetWidth();
102 pRefData->m_nHeight = rIcon.GetHeight();
103 pRefData->m_nDepth = wxDisplayDepth();
104
105 pRefData->m_hBitmap = (WXHBITMAP)rIcon.GetHandle();
106 // no mask???
107 pRefData->m_pBitmapMask = new wxMask();
108
109#if WXWIN_COMPATIBILITY_2
110 pRefData->m_bOk = TRUE;
111#endif // WXWIN_COMPATIBILITY_2
112 return(TRUE);
113}
114
115bool wxBitmap::CopyFromCursor(
116 const wxCursor& rCursor
117)
118{
119 UnRef();
120
121 if (!rCursor.Ok())
122 return(FALSE);
123 return(CopyFromIconOrCursor(rCursor));
124}
125
126bool wxBitmap::CopyFromIcon(
127 const wxIcon& rIcon
128)
129{
130 UnRef();
131
132 if (!rIcon.Ok())
133 return(FALSE);
134
135 return CopyFromIconOrCursor(rIcon);
136}
137
138wxBitmap::~wxBitmap()
139{
140 if (wxTheBitmapList)
141 wxTheBitmapList->DeleteObject(this);
142}
143
144wxBitmap::wxBitmap(
145 const char zBits[]
146, int nTheWidth
147, int nTheHeight
148, int nNoBits
149)
150{
151 Init();
152
153 wxBitmapRefData* pRefData = new wxBitmapRefData;
154 BITMAPINFOHEADER2 vHeader;
155 BITMAPINFO2 vInfo;
156 HDC hDc;
157 HPS hPs;
158 DEVOPENSTRUC vDop = { NULL, "DISPLAY", NULL, NULL, NULL, NULL, NULL, NULL, NULL };
159 SIZEL vSize = {0, 0};
160
161 wxASSERT(vHabmain != NULL);
162
163 hDc = ::DevOpenDC(vHabmain, OD_MEMORY, (PSZ)"*", 1L, (PDEVOPENDATA)&vDop, 0L);
164
165 vHeader.cbFix = sizeof(vHeader);
166 vHeader.cx = (USHORT)nTheWidth;
167 vHeader.cy = (USHORT)nTheHeight;
168 vHeader.cPlanes = 1L;
169 vHeader.cBitCount = nNoBits;
170 vHeader.ulCompression = BCA_UNCOMP;
171 vHeader.cxResolution = 0;
172 vHeader.cyResolution = 0;
173 vHeader.cclrUsed = 0;
174 vHeader.cclrImportant = 0;
175 vHeader.usUnits = BRU_METRIC;
176 vHeader.usRecording = BRA_BOTTOMUP;
177 vHeader.usRendering = BRH_NOTHALFTONED;
178 vHeader.cSize1 = 0;
179 vHeader.cSize2 = 0;
180 vHeader.ulColorEncoding = 0;
181 vHeader.ulIdentifier = 0;
182
183 hPs = ::GpiCreatePS(vHabmain, hDc, &vSize, GPIA_ASSOC | PU_PELS);
184 if (hPs == 0)
185 {
186 wxLogLastError("GpiCreatePS Failure");
187 }
188
189 m_refData = pRefData;
190
191 pRefData->m_nWidth = nTheWidth;
192 pRefData->m_nHeight = nTheHeight;
193 pRefData->m_nDepth = nNoBits;
194 pRefData->m_nNumColors = 0;
195 pRefData->m_pSelectedInto = NULL;
196
197 HBITMAP hBmp = ::GpiCreateBitmap(hPs, &vHeader, 0L, NULL, &vInfo);
198 if (!hBmp)
199 {
200 wxLogLastError("CreateBitmap");
201 }
202 SetHBITMAP((WXHBITMAP)hBmp);
203}
204
205// Create from XPM data
206wxBitmap::wxBitmap(
207 char** ppData
208, wxControl* WXUNUSED(pAnItem))
209{
210 Init();
211
212 (void)Create( (void *)ppData
213 ,wxBITMAP_TYPE_XPM_DATA
214 ,0
215 ,0
216 ,0
217 );
218}
219
220wxBitmap::wxBitmap(
221 int nW
222, int nH
223, int nD
224)
225{
226 Init();
227
228 (void)Create( nW
229 ,nH
230 ,nD
231 );
232}
233
234wxBitmap::wxBitmap(
235 void* pData
236, long lType
237, int nWidth
238, int nHeight
239, int nDepth
240)
241{
242 Init();
243
244 (void)Create( pData
245 ,lType
246 ,nWidth
247 ,nHeight
248 ,nDepth
249 );
250}
251
252wxBitmap::wxBitmap(
253 const wxString& rFilename
254, long lType
255)
256{
257 Init();
258
259 LoadFile( rFilename
260 ,(int)lType
261 );
262}
263
264bool wxBitmap::Create(
265 int nW
266, int nH
267, int nD
268)
269{
270 HBITMAP hBmp;
271 BITMAPINFOHEADER2 vHeader;
272 BITMAPINFO2 vInfo;
273 HPS hpsScreen;
274 HDC hdcScreen;
275 DEVOPENSTRUC vDop = { NULL, "DISPLAY", NULL, NULL, NULL, NULL, NULL, NULL, NULL };
276 SIZEL vSize = {0, 0};
277 LONG lBitCount;
278
279 wxASSERT(vHabmain != NULL);
280
281 hpsScreen = ::WinGetScreenPS(HWND_DESKTOP);
282 hdcScreen = ::GpiQueryDevice(hpsScreen);
283 ::DevQueryCaps(hdcScreen, CAPS_COLOR_BITCOUNT, 1L, &lBitCount);
284
285 vHeader.cbFix = sizeof(vHeader);
286 vHeader.cx = (USHORT)nW;
287 vHeader.cy = (USHORT)nH;
288 vHeader.cPlanes = (USHORT)nD;
289 vHeader.cBitCount = lBitCount;
290 vHeader.ulCompression = BCA_UNCOMP;
291 vHeader.cxResolution = 0;
292 vHeader.cyResolution = 0;
293 vHeader.cclrUsed = 0;
294 vHeader.cclrImportant = 0;
295 vHeader.usUnits = BRU_METRIC;
296 vHeader.usRecording = BRA_BOTTOMUP;
297 vHeader.usRendering = BRH_NOTHALFTONED;
298 vHeader.cSize1 = 0;
299 vHeader.cSize2 = 0;
300 vHeader.ulColorEncoding = 0;
301 vHeader.ulIdentifier = 0;
302
303 UnRef();
304 m_refData = new wxBitmapRefData;
305
306 GetBitmapData()->m_nWidth = nW;
307 GetBitmapData()->m_nHeight = nH;
308 GetBitmapData()->m_nDepth = nD;
309
310 if (nD > 0)
311 {
312 hBmp = ::GpiCreateBitmap(hpsScreen, &vHeader, 0L, NULL, &vInfo);
313 if (!hBmp)
314 {
315 wxLogLastError("CreateBitmap");
316 }
317 }
318 else
319 {
320 LONG lPlanes;
321
322 ::DevQueryCaps(hdcScreen, CAPS_COLOR_PLANES, 1L, &lPlanes);
323 hBmp = ::GpiCreateBitmap(hpsScreen, &vHeader, 0L, NULL, &vInfo);
324 if (!hBmp)
325 {
326 wxLogLastError("CreateBitmap");
327 }
328 GetBitmapData()->m_nDepth = wxDisplayDepth();
329 }
330 SetHBITMAP((WXHBITMAP)hBmp);
331
332#if WXWIN_COMPATIBILITY_2
333 GetBitmapData()->m_bOk = hBmp != 0;
334#endif // WXWIN_COMPATIBILITY_2
335
336 return Ok();
337}
338
339bool wxBitmap::LoadFile(
340 const wxString& rFilename
341, long lType
342)
343{
344 HPS hPs = NULLHANDLE;
345
346 UnRef();
347
348 wxBitmapHandler* pHandler = wxDynamicCast( FindHandler(lType)
349 ,wxBitmapHandler
350 );
351
352 if (pHandler)
353 {
354 m_refData = new wxBitmapRefData;
355
356 return(pHandler->LoadFile( this
357 ,rFilename
358 ,hPs
359 ,lType
360 , -1
361 , -1
362 ));
363 }
364 else
365 {
366 wxImage vImage;
367
368 if (!vImage.LoadFile(rFilename, lType) || !vImage.Ok() )
369 return(FALSE);
370
371 *this = vImage.ConvertToBitmap();
372
373 return(TRUE);
374 }
375}
376
377bool wxBitmap::Create(
378 void* pData
379, long lType
380, int nWidth
381, int nHeight
382, int nDepth
383)
384{
385 UnRef();
386
387 wxBitmapHandler* pHandler = wxDynamicCast( FindHandler(lType)
388 ,wxBitmapHandler
389 );
390
391 if (!pHandler)
392 {
393 wxLogDebug(wxT("Failed to create bitmap: no bitmap handler for "
394 "type %d defined."), lType);
395
396 return(FALSE);
397 }
398
399 m_refData = new wxBitmapRefData;
400
401 return(pHandler->Create( this
402 ,pData
403 ,lType
404 ,nWidth
405 ,nHeight
406 ,nDepth
407 ));
408}
409
410bool wxBitmap::SaveFile(
411 const wxString& rFilename
412, int lType
413, const wxPalette* pPalette
414)
415{
416 wxBitmapHandler* pHandler = wxDynamicCast( FindHandler(lType)
417 ,wxBitmapHandler
418 );
419
420 if (pHandler)
421 {
422 return pHandler->SaveFile( this
423 ,rFilename
424 ,lType
425 ,pPalette
426 );
427 }
428 else
429 {
430 // FIXME what about palette? shouldn't we use it?
431 wxImage vImage(*this);
432
433 if (!vImage.Ok())
434 return(FALSE);
435
436 return(vImage.SaveFile( rFilename
437 ,lType
438 ));
439 }
440}
441
442// ----------------------------------------------------------------------------
443// wxBitmap accessors
444// ----------------------------------------------------------------------------
445
446void wxBitmap::SetQuality(
447 int nQ
448)
449{
450 EnsureHasData();
451
452 GetBitmapData()->m_nQuality = nQ;
453}
454
455#if WXWIN_COMPATIBILITY_2
456void wxBitmap::SetOk(
457 bool bOk
458)
459{
460 EnsureHasData();
461
462 GetBitmapData()->m_bOk = bOk;
463}
464#endif // WXWIN_COMPATIBILITY_2
465
466void wxBitmap::SetPalette(
467 const wxPalette& rPalette
468)
469{
470 EnsureHasData();
471
472 GetBitmapData()->m_vBitmapPalette = rPalette;
473}
474
475void wxBitmap::SetMask(
476 wxMask* pMask
477)
478{
479 EnsureHasData();
480
481 GetBitmapData()->m_pBitmapMask = pMask;
482}
483
484// Will try something for OS/2 but not really sure how close
485// to the msw intent this is.
486wxBitmap wxBitmap::GetBitmapForDC(
487 wxDC& rDc
488) const
489{
490 wxMemoryDC vMemDC;
491 wxBitmap vTmpBitmap( this->GetWidth()
492 ,this->GetHeight()
493 ,rDc.GetDepth()
494 );
495 WXHBITMAP vOldBitmap;
496 HPS hMemoryPS;
497 HPS hPs;
498 POINTL vPoint[4];
499 SIZEL vSize = {0,0};
500
501 hMemoryPS = ::GpiCreatePS(vHabmain, (HDC)vMemDC.GetHDC(), &vSize, PU_PELS | GPIT_MICRO | GPIA_ASSOC);
502 hPs = ::GpiCreatePS(vHabmain, (HDC)rDc.GetHDC(), &vSize, PU_PELS | GPIT_MICRO | GPIA_ASSOC);
503
504 // TODO: Set the points
505
506 vOldBitmap = (WXHBITMAP)::GpiSetBitmap(hPs, (HBITMAP)vTmpBitmap.GetHBITMAP());
507 ::GpiBitBlt(hPs, hMemoryPS, 4L, vPoint, ROP_SRCCOPY, BBO_IGNORE);
508
509 return(vTmpBitmap);
510}
511
512// ----------------------------------------------------------------------------
513// wxMask
514// ----------------------------------------------------------------------------
515
516wxMask::wxMask()
517{
518 m_hMaskBitmap = 0;
519}
520
521// Construct a mask from a bitmap and a colour indicating
522// the transparent area
523wxMask::wxMask(
524 const wxBitmap& rBitmap
525, const wxColour& rColour
526)
527{
528 m_hMaskBitmap = 0;
529 Create( rBitmap
530 ,rColour
531 );
532}
533
534// Construct a mask from a bitmap and a palette index indicating
535// the transparent area
536wxMask::wxMask(
537 const wxBitmap& rBitmap
538, int nPaletteIndex
539)
540{
541 m_hMaskBitmap = 0;
542 Create( rBitmap
543 ,nPaletteIndex
544 );
545}
546
547// Construct a mask from a mono bitmap (copies the bitmap).
548wxMask::wxMask(
549 const wxBitmap& rBitmap
550)
551{
552 m_hMaskBitmap = 0;
553 Create(rBitmap);
554}
555
556wxMask::~wxMask()
557{
558 if (m_hMaskBitmap)
559 ::GpiDeleteBitmap((HBITMAP)m_hMaskBitmap);
560}
561
562// Create a mask from a mono bitmap (copies the bitmap).
563bool wxMask::Create(
564 const wxBitmap& rBitmap
565)
566{
567 BITMAPINFOHEADER2 vHeader;
568 DEVOPENSTRUC vDop = { NULL, "DISPLAY", NULL, NULL, NULL, NULL, NULL, NULL, NULL };
569 SIZEL vSize = {0, 0};
570 POINTL vPoint[4];
571
572 if (m_hMaskBitmap)
573 {
574 ::GpiDeleteBitmap((HBITMAP) m_hMaskBitmap);
575 m_hMaskBitmap = 0;
576 }
577 if (!rBitmap.Ok() || rBitmap.GetDepth() != 1)
578 {
579 return(FALSE);
580 }
581 vHeader.cbFix = sizeof(vHeader);
582 vHeader.cx = (USHORT)rBitmap.GetWidth();
583 vHeader.cy = (USHORT)rBitmap.GetHeight();
584 vHeader.cPlanes = 1;
585 vHeader.cBitCount = 1;
586
587 m_hMaskBitmap = (WXHBITMAP) ::GpiCreateBitmap( m_hPs
588 ,&vHeader
589 ,0L
590 ,NULL
591 ,NULL
592 );
593
594 HPS srcPS = ::GpiCreatePS(vHabmain, m_hDc, &vSize, PU_PELS | GPIT_MICRO | GPIA_ASSOC);
595 ::GpiSetBitmap(srcPS, (HBITMAP)rBitmap.GetHBITMAP());
596 HPS destPS = ::GpiCreatePS(vHabmain, m_hDc, &vSize, PU_PELS | GPIT_MICRO | GPIA_ASSOC);
597 ::GpiSetBitmap(srcPS, (HBITMAP)m_hMaskBitmap);
598 // TODO: Set the point array
599 ::GpiBitBlt(destPS, srcPS, 4L, vPoint, ROP_SRCCOPY , BBO_IGNORE);
600
601 ::GpiDestroyPS(srcPS);
602 ::GpiDestroyPS(destPS);
603 return(TRUE);
604}
605
606// Create a mask from a bitmap and a palette index indicating
607// the transparent area
608bool wxMask::Create(
609 const wxBitmap& rBitmap
610, int nPaletteIndex
611)
612{
613 if (m_hMaskBitmap)
614 {
615 ::GpiDeleteBitmap((HBITMAP) m_hMaskBitmap);
616 m_hMaskBitmap = 0;
617 }
618 if (rBitmap.Ok() && rBitmap.GetPalette()->Ok())
619 {
620 unsigned char cRed;
621 unsigned char cGreen;
622 unsigned char cBlue;
623
624 if (rBitmap.GetPalette()->GetRGB( nPaletteIndex
625 ,&cRed
626 ,&cGreen
627 ,&cBlue
628 ))
629 {
630 wxColour vTransparentColour( cRed
631 ,cGreen
632 ,cBlue
633 );
634
635 return (Create( rBitmap
636 ,vTransparentColour
637 ));
638 }
639 }
640 return(FALSE);
641}
642
643// Create a mask from a bitmap and a colour indicating
644// the transparent area
645bool wxMask::Create(
646 const wxBitmap& rBitmap
647, const wxColour& rColour
648)
649{
650 BITMAPINFOHEADER2 vHeader;
651 DEVOPENSTRUC vDop = { NULL, "DISPLAY", NULL, NULL, NULL, NULL, NULL, NULL, NULL };
652 SIZEL vSize = {0, 0};
653 POINTL vPoint[4];
654
655 if (m_hMaskBitmap)
656 {
657 ::GpiDeleteBitmap((HBITMAP) m_hMaskBitmap);
658 m_hMaskBitmap = 0;
659 }
660 if (!rBitmap.Ok())
661 {
662 return(FALSE);
663 }
664
665 // scan the bitmap for the transparent colour and set
666 // the corresponding pixels in the mask to BLACK and
667 // the rest to WHITE
668 COLORREF vMaskColour = OS2RGB(rColour.Red(), rColour.Green(), rColour.Blue());
669
670 vHeader.cbFix = sizeof(vHeader);
671 vHeader.cx = (USHORT)rBitmap.GetWidth();
672 vHeader.cy = (USHORT)rBitmap.GetHeight();
673 vHeader.cPlanes = 1;
674 vHeader.cBitCount = 1;
675
676 m_hMaskBitmap = (WXHBITMAP) ::GpiCreateBitmap( m_hPs
677 ,&vHeader
678 ,0L
679 ,NULL
680 ,NULL
681 );
682
683 HPS srcPS = ::GpiCreatePS(vHabmain, m_hDc, &vSize, PU_PELS | GPIT_MICRO | GPIA_ASSOC);
684 ::GpiSetBitmap(srcPS, (HBITMAP)rBitmap.GetHBITMAP());
685 HPS destPS = ::GpiCreatePS(vHabmain, m_hDc, &vSize, PU_PELS | GPIT_MICRO | GPIA_ASSOC);
686 ::GpiSetBitmap(srcPS, (HBITMAP)m_hMaskBitmap);
687
688 // this is not very efficient, but I can't think
689 // of a better way of doing it
690 for (int w = 0; w < rBitmap.GetWidth(); w++)
691 {
692 for (int h = 0; h < rBitmap.GetHeight(); h++)
693 {
694 POINTL vPoint;
695
696 vPoint.x = w;
697 vPoint.y = h;
698
699 COLORREF col = ::GpiQueryPel(srcPS, &vPoint);
700
701 if (col == vMaskColour)
702 {
703 ::GpiSetColor(destPS, CLR_WHITE);
704 ::GpiSetPel(destPS, &vPoint);
705 }
706 else
707 {
708 ::GpiSetColor(destPS, CLR_BLACK);
709 ::GpiSetPel(destPS, &vPoint);
710 }
711 }
712 }
713 ::GpiDestroyPS(srcPS);
714 ::GpiDestroyPS(destPS);
715 return(TRUE);
716}
717
718// ----------------------------------------------------------------------------
719// wxBitmapHandler
720// ----------------------------------------------------------------------------
721
722bool wxBitmapHandler::Create(
723 wxGDIImage* pImage
724, void* pData
725, long lFlags
726, int nWidth
727, int nHeight
728, int nDepth
729)
730{
731 wxBitmap* pBitmap = wxDynamicCast( pImage
732 ,wxBitmap
733 );
734
735 return(pBitmap ? Create( pBitmap
736 ,pData
737 ,nWidth
738 ,nHeight
739 ,nDepth
740 ) : FALSE);
741}
742
743bool wxBitmapHandler::Load(
744 wxGDIImage* pImage
745, const wxString& rName
746, HPS hPs
747, long lFlags
748, int nWidth
749, int nHeight
750)
751{
752 wxBitmap* pBitmap = wxDynamicCast( pImage
753 ,wxBitmap
754 );
755
756 return(pBitmap ? LoadFile( pBitmap
757 ,rName
758 ,hPs
759 ,lFlags
760 ,nWidth
761 ,nHeight
762 ) : FALSE);
763}
764
765bool wxBitmapHandler::Save(
766 wxGDIImage* pImage
767, const wxString& rName
768, int lType
769)
770{
771 wxBitmap* pBitmap = wxDynamicCast( pImage
772 ,wxBitmap
773 );
774
775 return(pBitmap ? SaveFile( pBitmap
776 ,rName
777 ,lType
778 ) : FALSE);
779}
780
781bool wxBitmapHandler::Create(
782 wxBitmap* WXUNUSED(pBitmap)
783, void* WXUNUSED(pData)
784, long WXUNUSED(lType)
785, int WXUNUSED(nWidth)
786, int WXUNUSED(nHeight)
787, int WXUNUSED(nDepth)
788)
789{
790 return(FALSE);
791}
792
793bool wxBitmapHandler::LoadFile(
794 wxBitmap* WXUNUSED(pBitmap)
795, const wxString& WXUNUSED(rName)
796, HPS WXUNUSED(hPs)
797, long WXUNUSED(lType)
798, int WXUNUSED(nDesiredWidth)
799, int WXUNUSED(nDesiredHeight)
800)
801{
802 return(FALSE);
803}
804
805bool wxBitmapHandler::SaveFile(
806 wxBitmap* WXUNUSED(pBitmap)
807, const wxString& WXUNUSED(rName)
808, int WXUNUSED(nType)
809, const wxPalette* WXUNUSED(pPalette)
810)
811{
812 return(FALSE);
813}
814