#include "Pomme.h" #include "PommeFiles.h" #include "PommeGraphics.h" #include "PommeMemory.h" #include "SysFont.h" #include "Utilities/memstream.h" #include #include #include using namespace Pomme; using namespace Pomme::Graphics; static bool IntersectRects(const Rect* r1, Rect* r2) { r2->left = std::max(r1->left, r2->left); r2->right = std::min(r1->right, r2->right); r2->top = std::max(r1->top, r2->top); r2->bottom = std::min(r1->bottom, r2->bottom); return r2->left < r2->right && r2->top < r2->bottom; } // ---------------------------------------------------------------------------- - // Types struct GrafPortImpl { GrafPort port; ARGBPixmap pixels; bool dirty; Rect dirtyRect; PixMap macpm; PixMap* macpmPtr; GrafPortImpl(const Rect boundsRect) : port({boundsRect, this}) , pixels(boundsRect.right - boundsRect.left, boundsRect.bottom - boundsRect.top) , dirty(false) { macpm = {}; macpm.bounds = boundsRect; macpm.pixelSize = 32; macpm.rowBytes = (pixels.width * macpm.pixelSize / 8) | (1 << 15); // bit 15 = 1: structure is PixMap, not BitMap macpm._impl = (Ptr) &pixels; macpmPtr = &macpm; } void DamageRegion(const Rect& r) { if (!dirty) { dirtyRect = r; } else { // Already dirty, expand existing dirty rect dirtyRect.top = std::min(dirtyRect.top, r.top); dirtyRect.left = std::min(dirtyRect.left, r.left); dirtyRect.bottom = std::max(dirtyRect.bottom, r.bottom); dirtyRect.right = std::max(dirtyRect.right, r.right); } dirty = true; } void DamageRegion(SInt16 x, SInt16 y, SInt16 w, SInt16 h) { Rect r = {y, x, static_cast(y + h), static_cast(x + w)}; DamageRegion(r); } ~GrafPortImpl() { macpm._impl = nullptr; } }; // ---------------------------------------------------------------------------- - // Internal State static std::unique_ptr screenPort = nullptr; static GrafPortImpl* curPort = nullptr; // Pen colors are stored as ARGB in the host's native endianness static UInt32 penFG = 0xFF'FF'00'FF; static UInt32 penBG = 0xFF'00'00'FF; static int penX = 0; static int penY = 0; // ---------------------------------------------------------------------------- - // Initialization CGrafPtr Pomme::Graphics::GetScreenPort(void) { return &screenPort->port; } void Pomme::Graphics::Init() { Rect boundsRect = {0, 0, 480, 640}; screenPort = std::make_unique(boundsRect); curPort = screenPort.get(); } // ---------------------------------------------------------------------------- - // Internal utils static UInt32 GetEightColorPaletteValue(long color) { switch (color) { case whiteColor: return clut4[0]; case yellowColor: return clut4[1]; case redColor: return clut4[3]; case magentaColor: return clut4[4]; case blackColor: return clut4[15]; case cyanColor: return clut4[7]; case greenColor: return clut4[8]; // I'm assuming this is light green rather than dark case blueColor: return clut4[6]; default: return 0xFF'FF'00'FF; } } // ---------------------------------------------------------------------------- - // Errors OSErr QDError(void) { TODOMINOR(); return noErr; } // ---------------------------------------------------------------------------- - // PICT resources static PicHandle GetPictureFromStream(std::istream& stream, bool skip512) { ARGBPixmap pm = ReadPICT(stream, skip512); // Tack the data onto the end of the Picture struct, // so that DisposeHandle frees both the Picture and the data. PicHandle ph = (PicHandle) NewHandle(int(sizeof(Picture) + pm.data.size())); Picture& pic = **ph; Ptr pixels = (Ptr) *ph + sizeof(Picture); pic.picFrame = Rect{0, 0, (SInt16) pm.height, (SInt16) pm.width}; pic.picSize = -1; pic.__pomme_pixelsARGB32 = pixels; memcpy(pic.__pomme_pixelsARGB32, pm.data.data(), pm.data.size()); return ph; } PicHandle GetPicture(short PICTresourceID) { Handle rawResource = GetResource('PICT', PICTresourceID); if (rawResource == nil) return nil; memstream stream(*rawResource, GetHandleSize(rawResource)); PicHandle ph = GetPictureFromStream(stream, false); ReleaseResource(rawResource); return ph; } PicHandle GetPictureFromFile(const FSSpec* spec) { short refNum; OSErr error = FSpOpenDF(spec, fsRdPerm, &refNum); if (error != noErr) return nil; auto& stream = Pomme::Files::GetStream(refNum); PicHandle ph = GetPictureFromStream(stream, true); FSClose(refNum); return ph; } // ---------------------------------------------------------------------------- - // Rect void SetRect(Rect* r, short left, short top, short right, short bottom) { r->left = left; r->top = top; r->right = right; r->bottom = bottom; } void OffsetRect(Rect* r, short dh, short dv) { r->left += dh; r->right += dh; r->top += dv; r->bottom += dv; } // ---------------------------------------------------------------------------- - // GWorld static inline GrafPortImpl& GetImpl(GWorldPtr offscreenGWorld) { return *(GrafPortImpl*) offscreenGWorld->_impl; } static inline ARGBPixmap& GetImpl(PixMapPtr pixMap) { return *(ARGBPixmap*) pixMap->_impl; } OSErr NewGWorld(GWorldPtr* offscreenGWorld, short pixelDepth, const Rect* boundsRect, void* junk1, void* junk2, long junk3) { (void) pixelDepth; (void) junk1; (void) junk2; (void) junk3; GrafPortImpl* impl = new GrafPortImpl(*boundsRect); *offscreenGWorld = &impl->port; return noErr; } void DisposeGWorld(GWorldPtr offscreenGWorld) { delete &GetImpl(offscreenGWorld); } void GetGWorld(CGrafPtr* port, GDHandle* gdh) { *port = &curPort->port; *gdh = nil; } void SetGWorld(CGrafPtr port, GDHandle gdh) { (void) gdh; SetPort(port); } PixMapHandle GetGWorldPixMap(GWorldPtr offscreenGWorld) { return &GetImpl(offscreenGWorld).macpmPtr; } Ptr GetPixBaseAddr(PixMapHandle pm) { return (Ptr) GetImpl(*pm).data.data(); } // ---------------------------------------------------------------------------- - // Port void SetPort(GrafPtr port) { curPort = &GetImpl(port); } void GetPort(GrafPtr* outPort) { *outPort = &curPort->port; } Boolean IsPortDamaged(void) { return curPort->dirty; } void GetPortDamageRegion(Rect* r) { *r = curPort->dirtyRect; } void ClearPortDamage(void) { curPort->dirty = false; } void DamagePortRegion(const Rect* r) { curPort->DamageRegion(*r); } CGrafPtr GetWindowPort(WindowPtr window) { return window; } PixMap* GetPortBitMapForCopyBits(CGrafPtr window) { return GetImpl(window).macpmPtr; } Rect* GetPortBounds(CGrafPtr port, Rect* rect) { *rect = port->portRect; return rect; } void DumpPortTGA(const char* outPath) { curPort->pixels.WriteTGA(outPath); } // ---------------------------------------------------------------------------- - // Pen state manipulation void MoveTo(short h, short v) { penX = h; penY = v; } void ForeColor(long color) { penFG = GetEightColorPaletteValue(color); } void BackColor(long color) { penBG = GetEightColorPaletteValue(color); } void GetForeColor(RGBColor* rgb) { rgb->red = (penFG >> 16 & 0xFF) << 8; rgb->green = (penFG >> 8 & 0xFF) << 8; rgb->blue = (penFG & 0xFF) << 8; } void RGBBackColor(const RGBColor* color) { penBG = 0xFF'00'00'00 | ((color->red >> 8) << 16) | ((color->green >> 8) << 8) | (color->blue >> 8) ; } void RGBForeColor(const RGBColor* color) { penFG = 0xFF'00'00'00 | ((color->red >> 8) << 16) | ((color->green >> 8) << 8) | (color->blue >> 8) ; } void RGBBackColor2(const UInt32 color) { penBG = 0xFF000000 | (color & 0x00FFFFFF); } void RGBForeColor2(const UInt32 color) { penFG = 0xFF000000 | (color & 0x00FFFFFF); } void PenNormal(void) { TODOMINOR(); } void PenSize(short width, short height) { (void) width; (void) height; TODOMINOR(); } // ---------------------------------------------------------------------------- - // Paint static void _FillRect(const int left, const int top, const int right, const int bottom, UInt32 fillColor) { if (!curPort) { throw std::runtime_error("_FillRect: no port set"); } Rect dstRect; dstRect.left = left; dstRect.top = top; dstRect.right = right; dstRect.bottom = bottom; Rect clippedDstRect = dstRect; if (!IntersectRects(&curPort->port.portRect, &clippedDstRect)) { return; } curPort->DamageRegion(clippedDstRect); fillColor = PackU32BE(&fillColor); // convert to big-endian UInt32* dst = curPort->pixels.GetPtr(clippedDstRect.left, clippedDstRect.top); for (int y = clippedDstRect.top; y < clippedDstRect.bottom; y++) { for (int x = 0; x < clippedDstRect.right - clippedDstRect.left; x++) { dst[x] = fillColor; } dst += curPort->pixels.width; } } void PaintRect(const struct Rect* r) { _FillRect(r->left, r->top, r->right, r->bottom, penFG); } void EraseRect(const struct Rect* r) { _FillRect(r->left, r->top, r->right, r->bottom, penBG); } void LineTo(short x1, short y1) { UInt32 color = PackU32BE(&penFG); auto offx = curPort->port.portRect.left; auto offy = curPort->port.portRect.top; int x0 = penX; int y0 = penY; int dx = std::abs(x1 - x0); int sx = x0 < x1 ? 1 : -1; int dy = -std::abs(y1 - y0); int sy = y0 < y1 ? 1 : -1; int err = dx + dy; curPort->DamageRegion(penX, penY, dx, dy); while (1) { curPort->pixels.Plot(x0 - offx, y0 - offy, color); if (x0 == x1 && y0 == y1) break; int e2 = 2 * err; if (e2 >= dy) { err += dy; x0 += sx; } if (e2 <= dx) { err += dx; y0 += sy; } } penX = x0; penY = y0; } void FrameRect(const Rect* r) { UInt32 color = PackU32BE(&penFG); auto& pm = curPort->pixels; auto offx = curPort->port.portRect.left; auto offy = curPort->port.portRect.top; for (int x = r->left; x < r->right; x++) pm.Plot(x - offx, r->top - offy, color); for (int x = r->left; x < r->right; x++) pm.Plot(x - offx, r->bottom - 1 - offy, color); for (int y = r->top; y < r->bottom; y++) pm.Plot(r->left - offx, y - offy, color); for (int y = r->top; y < r->bottom; y++) pm.Plot(r->right - 1 - offx, y - offy, color); curPort->DamageRegion(*r); } void FrameArc(const Rect* r, short startAngle, short arcAngle) { (void) r; (void) startAngle; (void) arcAngle; TODOMINOR(); } void Pomme::Graphics::DrawARGBPixmap(int left, int top, ARGBPixmap& pixmap) { if (!curPort) { throw std::runtime_error("DrawARGBPixmap: no port set"); } Rect dstRect; dstRect.left = left; dstRect.top = top; dstRect.right = left + pixmap.width; dstRect.bottom = top + pixmap.height; Rect clippedDstRect = dstRect; if (!IntersectRects(&curPort->port.portRect, &clippedDstRect)) { return; // wholly outside bounds } curPort->DamageRegion(clippedDstRect); UInt32* src = pixmap.GetPtr(clippedDstRect.left - dstRect.left, clippedDstRect.top - dstRect.top); UInt32* dst = curPort->pixels.GetPtr(clippedDstRect.left, clippedDstRect.top); for (int y = clippedDstRect.top; y < clippedDstRect.bottom; y++) { memcpy(dst, src, Width(clippedDstRect) * sizeof(UInt32)); dst += curPort->pixels.width; src += pixmap.width; } } void DrawPicture(PicHandle myPicture, const Rect* dstRect) { auto& pic = **myPicture; UInt32* srcPixels = (UInt32*) pic.__pomme_pixelsARGB32; int dstWidth = Width(*dstRect); int dstHeight = Height(*dstRect); int srcWidth = Width(pic.picFrame); int srcHeight = Height(pic.picFrame); if (srcWidth != dstWidth || srcHeight != dstHeight) TODOFATAL2("we only support dstRect with the same width/height as the source picture"); for (int y = 0; y < dstHeight; y++) { memcpy( curPort->pixels.GetPtr(dstRect->left, dstRect->top + y), srcPixels + y * srcWidth, 4 * dstWidth); } curPort->DamageRegion(*dstRect); } void CopyBits( const PixMap* srcBits, PixMap* dstBits, const Rect* srcRect, const Rect* dstRect, short mode, void* maskRgn ) { (void) maskRgn; auto& srcPM = GetImpl((PixMapPtr) srcBits); auto& dstPM = GetImpl(dstBits); const auto& srcBounds = srcBits->bounds; const auto& dstBounds = dstBits->bounds; int srcRectWidth = Width(*srcRect); int srcRectHeight = Height(*srcRect); int dstRectWidth = Width(*dstRect); int dstRectHeight = Height(*dstRect); if (srcRectWidth != dstRectWidth || srcRectHeight != dstRectHeight) TODOFATAL2("can only copy between rects of same dimensions"); switch (mode) { case srcCopy: for (int y = 0; y < srcRectHeight; y++) { memcpy( dstPM.GetPtr(dstRect->left - dstBounds.left, dstRect->top - dstBounds.top + y), srcPM.GetPtr(srcRect->left - srcBounds.left, srcRect->top - srcBounds.top + y), 4 * srcRectWidth ); } break; case srcCopy|transparent: { // Replaces the destination pixel with the source pixel // if the source pixel is not equal to the background color. UInt32 transparentColor = penBG; #if !(__BIG_ENDIAN__) ByteswapInts(sizeof(transparentColor), 1, &transparentColor); // need to byteswap because ARGBPixmap.GetPtr returns a pointer to raw (big-endian) ARGB ints #endif for (int y = 0; y < srcRectHeight; y++) { UInt32* dstPix = dstPM.GetPtr(dstRect->left - dstBounds.left, dstRect->top - dstBounds.top + y); UInt32* srcPix = srcPM.GetPtr(srcRect->left - srcBounds.left, srcRect->top - srcBounds.top + y); for (int x = 0; x < srcRectWidth; x++) { if (*srcPix != transparentColor) { *dstPix = *srcPix; } dstPix++; srcPix++; } } break; } default: TODOFATAL2("unsupported CopyBits mode " << mode); break; } curPort->DamageRegion(*dstRect); } // ---------------------------------------------------------------------------- - // Text rendering short TextWidthC(const char* cstr) { if (!cstr) return 0; int totalWidth = -SysFont::charSpacing; for (; *cstr; cstr++) { totalWidth += SysFont::charSpacing; totalWidth += SysFont::GetGlyph(*cstr).width; } return totalWidth; } void DrawStringC(const char* cstr) { if (!cstr) return; _FillRect( penX, penY - SysFont::ascend, penX + TextWidthC(cstr), penY + SysFont::descend, penBG ); penX -= SysFont::charSpacing; for (; *cstr; cstr++) { penX += SysFont::charSpacing; DrawChar(*cstr); } } void DrawChar(char c) { UInt32 fg = PackU32BE(&penFG); auto& glyph = SysFont::GetGlyph(c); // Theoretical coordinates of top-left corner of glyph (may be outside port bounds!) Rect dstRect; dstRect.left = penX - SysFont::leftMargin; dstRect.top = penY - SysFont::ascend; dstRect.right = dstRect.left + SysFont::widthBits; dstRect.bottom = dstRect.top + SysFont::rows; // Advance pen position penX += glyph.width; Rect clippedDstRect = dstRect; if (!IntersectRects(&curPort->port.portRect, &clippedDstRect)) { return; // wholly outside bounds } curPort->DamageRegion(clippedDstRect); // Glyph boundaries int minCol = clippedDstRect.left - dstRect.left; int minRow = clippedDstRect.top - dstRect.top; auto* dst2 = curPort->pixels.GetPtr(clippedDstRect.left, clippedDstRect.top); for (int glyphY = minRow; glyphY < minRow + Height(clippedDstRect); glyphY++) { auto rowBits = glyph.bits[glyphY]; rowBits >>= minCol; auto* dstRow = dst2; for (int glyphX = minCol; glyphX < minCol + Width(clippedDstRect); glyphX++) { if (rowBits & 1) { *dstRow = fg; } rowBits >>= 1; dstRow++; } dst2 += curPort->pixels.width; } }