diff --git a/include/lgi/common/AudioView.h b/include/lgi/common/AudioView.h --- a/include/lgi/common/AudioView.h +++ b/include/lgi/common/AudioView.h @@ -1,771 +1,775 @@ /// Audio waveform display control #pragma once #include "lgi/common/Layout.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Menu.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/FileSelect.h" #define CC(code) LgiSwap32(code) class LAudioView : public LLayout { public: enum LFileType { AudioUnknown, AudioRaw, AudioWav, }; enum LSampleType { AudioS16LE, AudioS16BE, AudioS32LE, AudioS32BE, AudioFloat32, }; protected: enum LCmds { IDC_COPY_CURSOR, IDC_LOAD_WAV, IDC_LOAD_RAW, IDC_SAVE_WAV, IDC_SAVE_RAW, IDC_DRAW_AUTO, IDC_DRAW_LINE, IDC_SCAN_SAMPLES, IDC_SCAN_GROUPS, IDC_44K, IDC_48K, IDC_88K, IDC_96K, IDC_MONO, IDC_STEREO, IDC_2pt1_CHANNELS, IDC_5pt1_CHANNELS, }; enum LDrawMode { DrawAutoSelect, DrawSamples, ScanSamples, ScanGroups, }; LArray Audio; LFileType Type = AudioUnknown; LSampleType SampleType = AudioS16LE; int SampleBits = 0; int SampleRate = 0; int Channels = 0; size_t DataStart = 0; size_t CursorSample = 0; LRect Data; LArray ChData; double XZoom = 1.0; LString Msg, ErrorMsg; LDrawMode DrawMode = DrawAutoSelect; template struct Grp { T Min = 0, Max = 0; }; LArray>> Int16Grps; LArray>> Int32Grps; LArray>> FloatGrps; LArray> PixelAddr; void MouseToCursor(LMouse &m) { auto idx = ViewToSample(m.x); if (idx != CursorSample) { CursorSample = idx; UpdateMsg(); Invalidate(); } } void SetData(LRect &r) { Data = r; ChData.Length(Channels); int Dy = Data.Y() - 1; for (int i=0; i 0 && rd != f.GetSize()) Audio.Length(rd); auto Ext = LGetExtension(FileName); if (!Stricmp(Ext, "wav")) Type = AudioWav; else if (!Stricmp(Ext, "raw")) Type = AudioRaw; else { ErrorMsg.Printf("Unknown format: %s", Ext); return Empty(); } if (rate) SampleRate = rate; DataStart = 0; SampleBits = bitDepth; Channels = channels; if (bitDepth == 16) { SampleType = AudioS16LE; } else if (bitDepth == 32) { SampleType = AudioS32LE; } else if (Type == AudioWav) { // Parse the wave file... LPointer p; p.u8 = Audio.AddressOf(); auto end = p.u8 + Audio.Length(); if (*p.u32++ != CC('RIFF')) return Empty(); auto ChunkSz = *p.u32++; auto Fmt = *p.u32++; while (p.u8 < end) { auto SubChunkId = *p.u32++; auto SubChunkSz = *p.u32++; auto NextChunk = p.u8 + SubChunkSz; if (SubChunkId == CC('fmt ')) { auto AudioFmt = *p.u16++; Channels = *p.u16++; SampleRate = *p.u32++; auto ByteRate = *p.u32++; auto BlockAlign = *p.u16++; SampleBits = *p.u16++; if (SampleBits == 16) SampleType = AudioS16LE; else if (SampleBits == 32) SampleType = AudioS32LE; } else if (SubChunkId == CC('data')) { DataStart = p.u8 - Audio.AddressOf(); break; } p.u8 = NextChunk; } if (!DataStart) { ErrorMsg = "No 'data' element found."; return Empty(); } } else if (Type == AudioRaw) { // Assume 32bit SampleType = AudioS32LE; SampleBits = 32; } if (!SampleRate) SampleRate = 44100; if (!Channels) Channels = 2; Invalidate(); return true; } LRect DefaultPos() { auto c = GetClient(); c.Inset(20, 20); return c; } size_t GetSamples() { return (Audio.Length() - DataStart) / (SampleBits >> 3) / Channels; } int SampleToView(size_t idx) { return Data.x1 + (int)((idx * Data.X()) / GetSamples()); } size_t ViewToSample(int x /*px*/) { int offset = x - Data.x1; ssize_t samples = GetSamples(); ssize_t idx = offset * samples / Data.X(); if (idx < 0) idx = 0; else if (idx >= samples) idx = samples - 1; return idx; } void OnPosChange() { auto def = DefaultPos(); LRect d = Data; d.y1 = def.y1; d.y2 = def.y2; SetData(d); } #define CheckItem(Sub, Name, Id, Chk) \ { \ auto item = Sub->AppendItem(Name, Id); \ if (item && Chk) item->Checked(true); \ } void OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { LSubMenu s; s.AppendItem("Copy Cursor Address", IDC_COPY_CURSOR); s.AppendSeparator(); auto File = s.AppendSub("File"); File->AppendItem("Load Wav", IDC_LOAD_WAV); File->AppendItem("Load Raw", IDC_LOAD_RAW); File->AppendItem("Save Wav", IDC_SAVE_WAV); File->AppendItem("Save Raw", IDC_SAVE_RAW); auto Draw = s.AppendSub("Draw Mode"); CheckItem(Draw, "Auto", IDC_DRAW_AUTO, DrawMode == DrawAutoSelect); CheckItem(Draw, "Line", IDC_DRAW_LINE, DrawMode == DrawSamples); CheckItem(Draw, "Scan Samples", IDC_SCAN_SAMPLES, DrawMode == ScanSamples); CheckItem(Draw, "Scan Groups", IDC_SCAN_GROUPS, DrawMode == ScanGroups); auto Rate = s.AppendSub("Sample Rate"); CheckItem(Rate, "44.1k", IDC_44K, SampleRate == 44100); CheckItem(Rate, "48k", IDC_48K, SampleRate == 4800); CheckItem(Rate, "88.2k", IDC_88K, SampleRate == 44100*2); CheckItem(Rate, "96k", IDC_96K, SampleRate == 48000*2); auto Ch = s.AppendSub("Channels"); CheckItem(Ch, "Mono", IDC_MONO, Channels == 1); CheckItem(Ch, "Stereo", IDC_STEREO, Channels == 2); CheckItem(Ch, "2.1", IDC_2pt1_CHANNELS, Channels == 3); CheckItem(Ch, "5.1", IDC_5pt1_CHANNELS, Channels == 6); bool Update = false; switch (s.Float(this, m)) { case IDC_COPY_CURSOR: { LClipBoard clip(this); size_t addrVal = DataStart + (CursorSample * Channels * SampleBits / 8); LString addr; addr.Printf(LPrintfSizeT, addrVal); clip.Text(addr); break; } case IDC_SAVE_RAW: { - LFileSelect s; - s.Parent(this); - if (!s.Save()) - break; - - LFile out(s.Name(), O_WRITE); - if (out) + auto s = new LFileSelect; + s->Parent(this); + s->Save([this](auto s, auto ok) { - out.SetSize(0); + if (ok) + { + LFile out(s->Name(), O_WRITE); + if (out) + { + out.SetSize(0); - auto ptr = Audio.AddressOf(DataStart); - out.Write(ptr, Audio.Length() - DataStart); - } - else - LgiMsg(this, "Can't open '%s' for writing.", "Error", MB_OK, s.Name()); + auto ptr = Audio.AddressOf(DataStart); + out.Write(ptr, Audio.Length() - DataStart); + } + else + LgiMsg(this, "Can't open '%s' for writing.", "Error", MB_OK, s->Name()); + } + delete s; + }); break; } case IDC_DRAW_AUTO: DrawMode = DrawAutoSelect; Update = true; break; case IDC_DRAW_LINE: DrawMode = DrawSamples; Update = true; break; case IDC_SCAN_SAMPLES: DrawMode = ScanSamples; Update = true; break; case IDC_SCAN_GROUPS: DrawMode = ScanGroups; Update = true; break; case IDC_44K: SampleRate = 44100; Update = true; break; case IDC_48K: SampleRate = 48000; Update = true; break; case IDC_88K: SampleRate = 44100*2; Update = true; break; case IDC_96K: SampleRate = 48000*2; Update = true; break; case IDC_MONO: Channels = 1; Update = true; break; case IDC_STEREO: Channels = 2; Update = true; break; case IDC_2pt1_CHANNELS: Channels = 3; Update = true; break; case IDC_5pt1_CHANNELS: Channels = 6; Update = true; break; default: break; } if (Update) { Int16Grps.Length(0); Int32Grps.Length(0); FloatGrps.Length(0); SetData(Data); Invalidate(); } } else if (m.Left()) { Focus(true); MouseToCursor(m); } } void OnMouseMove(LMouse &m) { if (m.Down()) MouseToCursor(m); } LPointer AddressOf(size_t Sample) { LPointer p; int SampleBytes = SampleBits >> 3; p.u8 = Audio.AddressOf(DataStart + (Sample * Channels * SampleBytes)); return p; } int16_t Native(int16_t &i) { if (SampleType == AudioS16BE) return LgiSwap16(i); return i; } int32_t Native(int32_t &i) { if (SampleType == AudioS32BE) return LgiSwap32(i); return i; } float Native(float &i) { return i; } void UpdateMsg() { ssize_t Samples = GetSamples(); auto Addr = AddressOf(CursorSample); size_t AddrOffset = Addr.u8 - Audio.AddressOf(); LString::Array Val; size_t GraphLen = 0; for (int ch=0; ch void PaintSamples(LSurface *pDC, int ChannelIdx, LArray>> *Grps) { #if PROFILE_PAINT_SAMPLES LProfile Prof("PaintSamples", 10); #endif T MaxT = 0; if (sizeof(T) == 1) MaxT = 0x7f; else if (sizeof(T) == 2) MaxT = 0x7fff; else MaxT = (T)0x7fffffff; auto Client = GetClient(); auto &c = ChData[ChannelIdx]; auto start = (T*)Audio.AddressOf(DataStart); size_t samples = GetSamples(); auto end = start + (samples * Channels); int cy = c.y1 + (c.Y() / 2); pDC->Colour(cGrid); pDC->Box(&c); int blk = 64; if (Grps->Length() == 0) { // Initialize the graph PROF("GraphGen"); Grps->Length(Channels); for (int ch = 0; ch < Channels; ch++) { auto &GraphArr = (*Grps)[ch]; GraphArr.SetFixedLength(false); GraphArr.Length(0); int BlkChannels = blk * Channels; size_t BlkIndex = 0; for (auto p = start; p < end; p += BlkChannels) { int remain = MIN( (int)(end - p), BlkChannels ); auto &b = GraphArr[BlkIndex++]; b.Min = b.Max = Native(p[ch]); for (int i = Channels + ch; i < remain; i += Channels) { auto n = Native(p[i]); if (n < b.Min) b.Min = n; if (n > b.Max) b.Max = n; } } GraphArr.SetFixedLength(true); } } PROF("PrePaint"); int YScale = c.Y() / 2; int CursorX = SampleToView(CursorSample); double blkScale = Grps->Length() > 0 ? (double) (*Grps)[0].Length() / c.X() : 1.0; LDrawMode EffectiveMode = DrawMode; auto StartSample = ViewToSample(Client.x1); auto EndSample = ViewToSample(Client.x2); auto SampleRange = EndSample - StartSample; if (EffectiveMode == DrawAutoSelect) { if (SampleRange < (Client.X() << 2)) EffectiveMode = DrawSamples; else if (blkScale < 1.0) EffectiveMode = ScanSamples; else EffectiveMode = ScanGroups; } // LgiTrace("DrawRange: " LPrintfSizeT "->" LPrintfSizeT " (" LPrintfSizeT ") mode=%i\n", StartSample, EndSample, SampleRange, (int)EffectiveMode); if (EffectiveMode == DrawSamples) { auto pSample = start + (StartSample * Channels) + ChannelIdx; if (CursorX >= Client.x1 && CursorX <= Client.x2) { pDC->Colour(L_BLACK); pDC->VLine(CursorX, c.y1, c.y2); } pDC->Colour(cGrid); pDC->HLine(c.x1, c.x2, cy); // LgiTrace(" ptr=%i\n", (int)((char*)pSample-(char*)start)); // For all samples in the view space... pDC->Colour(cMax); LPoint Prev(-1, -1); for (auto idx = StartSample; idx <= EndSample + 1; idx++, pSample += Channels) { auto n = Native(*pSample); auto x = SampleToView(idx); auto y = (T) (cy - ((M)n * YScale / MaxT)); if (idx != StartSample) pDC->Line(Prev.x, Prev.y, (int)x, (int)y); Prev.Set((int)x, (int)y); } } else { // For all x coordinates in the view space.. for (int Vx=Client.x1; Vx<=Client.x2; Vx++) { if (Vx % 100 == 0) { PROF("Pixels"); } if (Vx < c.x1 || Vx > c.x2) continue; auto isCursor = CursorX == Vx; if (isCursor) { pDC->Colour(L_BLACK); pDC->VLine(Vx, c.y1, c.y2); } Grp Min, Max; StartSample = ViewToSample(Vx); EndSample = ViewToSample(Vx+1); if (EffectiveMode == ScanSamples) { // Scan individual samples auto pStart = start + (StartSample * Channels) + ChannelIdx; auto pEnd = start + (EndSample * Channels) + ChannelIdx; #if 0 if (Vx==Client.x1) { auto PosX = SampleToView((pStart - ChannelIdx - start) / Channels); LgiTrace(" ptr=%i " LPrintfSizeT "-" LPrintfSizeT " x=%i pos=%i\n", (int)((char*)pStart-(char*)start), StartSample, EndSample, Vx, PosX); } #endif for (auto i = pStart; i < pEnd; i += Channels) { auto n = Native(*i); Min.Min = MIN(Min.Min, n); Max.Max = MAX(Max.Max, n); } } else { // Scan the buckets auto &Graph = (*Grps)[ChannelIdx]; double blkStart = (double)StartSample * Graph.Length() / samples; double blkEnd = (double)EndSample * Graph.Length() / samples; #if 0 if (isCursor) LgiTrace("Blk %g-%g of " LPrintfSizeT "\n", blkStart, blkEnd, Graph.Length()); #endif for (int i=(int)floor(blkStart); i<(int)ceil(blkEnd); i++) { auto &b = Graph[i]; Min.Min = MIN(Min.Min, b.Min); Min.Max = MAX(Min.Max, b.Min); Max.Min = MIN(Max.Min, b.Max); Max.Max = MAX(Max.Max, b.Max); } } auto y1 = (T) (cy - ((M)Min.Min * YScale / MaxT)); auto y2 = (T) (cy - ((M)Max.Max * YScale / MaxT)); pDC->Colour(isCursor ? cCursorMax : cMax); pDC->VLine(Vx, (int)y1, (int)y2); if (EffectiveMode == ScanGroups) { y1 = (T) (cy - ((M)Min.Max * YScale / MaxT)); y2 = (T) (cy - ((M)Max.Min * YScale / MaxT)); pDC->Colour(isCursor ? cCursorMin : cMin); pDC->VLine(Vx, (int)y1, (int)y2); } } } } void OnPaint(LSurface *pDC) { #ifdef WINDOWS LDoubleBuffer DblBuf(pDC); #endif pDC->Colour(L_WORKSPACE); pDC->Rectangle(); if (!Data.Valid()) SetData(DefaultPos()); auto Fnt = GetFont(); LDisplayString ds(Fnt, ErrorMsg ? ErrorMsg : Msg); Fnt->Transparent(true); Fnt->Fore(ErrorMsg ? LColour::Red : LColour(L_LOW)); ds.Draw(pDC, 4, 4); if (ErrorMsg) return; switch (SampleType) { case AudioS16LE: case AudioS16BE: { for (int ch = 0; ch < Channels; ch++) PaintSamples(pDC, ch, &Int16Grps); break; } case AudioS32LE: case AudioS32BE: { for (int ch = 0; ch < Channels; ch++) PaintSamples(pDC, ch, &Int32Grps); break; } case AudioFloat32: { for (int ch = 0; ch < Channels; ch++) PaintSamples(pDC, ch, &FloatGrps); break; } } } }; diff --git a/src/common/Gdc2/Filters/Gif.cpp b/src/common/Gdc2/Filters/Gif.cpp --- a/src/common/Gdc2/Filters/Gif.cpp +++ b/src/common/Gdc2/Filters/Gif.cpp @@ -1,1092 +1,1098 @@ /*hdr ** FILE: Gif.cpp ** AUTHOR: Matthew Allen ** DATE: 8/9/1998 ** DESCRIPTION: Gif file filter ** ** Copyright (C) 1997-8, Matthew Allen ** fret@memecode.com */ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Lzw.h" #include "lgi/common/Variant.h" #include "lgi/common/Palette.h" #ifdef FILTER_UI // define the symbol FILTER_UI to access the gif save options dialog #include "TransparentDlg.h" #endif #define MAX_CODES 4095 class GdcGif : public LFilter { LSurface *pDC; LStream *s; int ProcessedScanlines; uint8_t BackgroundColour = 0; bool Transparent = false; // Old GIF coder stuff short linewidth; int lines; int pass; short curr_size; /* The current code size */ short clearc; /* Value for a clear code */ short ending; /* Value for a ending code */ short newcodes; /* First available code */ short top_slot; /* Highest code for current size */ short slot; /* Last read code */ short navail_bytes; /* # bytes left in block */ short nbits_left; /* # bits left in current byte */ uchar b1; /* Current byte */ uchar byte_buff[257]; /* Current block */ uchar *pbytes; /* Pointer to next byte in block */ uchar stack[MAX_CODES+1]; /* Stack for storing pixels */ uchar suffix[MAX_CODES+1]; /* Suffix table */ ushort prefix[MAX_CODES+1]; /* Prefix linked list */ int bad_code_count; int get_byte(); int out_line(uchar *pixels, int linewidth, int interlaced, int BitDepth); short init_exp(short size); short get_next_code(); short decoder(int BitDepth, uchar interlaced); public: GdcGif(); Format GetFormat() { return FmtGif; } int GetCapabilites() { return FILTER_CAP_READ | FILTER_CAP_WRITE; } IoStatus ReadImage(LSurface *pDC, LStream *In); IoStatus WriteImage(LStream *Out, LSurface *pDC); bool GetVariant(const char *n, LVariant &v, const char *a) { if (!_stricmp(n, LGI_FILTER_TYPE)) { v = "Gif"; } else if (!_stricmp(n, LGI_FILTER_EXTENSIONS)) { v = "GIF"; } else return false; return true; } }; // Filter factory // tells the application we're here class GdcGifFactory : public LFilterFactory { bool CheckFile(const char *File, int Access, const uchar *Hint) { if (Hint) { if (Hint[0] == 'G' && Hint[1] == 'I' && Hint[2] == 'F' && Hint[3] == '8' && Hint[4] == '9') { return true; } } return (File) ? stristr(File, ".gif") != 0 : false; } LFilter *NewObject() { return new GdcGif; } } GifFactory; // gif error codes #define OUT_OF_MEMORY -10 #define BAD_CODE_SIZE -20 #define READ_ERROR -1 #define WRITE_ERROR -2 #define OPEN_ERROR -3 #define CREATE_ERROR -4 long code_mask[13] = { 0, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF}; int GdcGif::get_byte() { uchar c; if (s->Read(&c, 1) == 1) return c; return READ_ERROR; } int GdcGif::out_line(uchar *pixels, int linewidth, int interlaced, int BitDepth) { if (lines >= pDC->Y()) return -1; auto pal = pDC->Palette(); /* static bool first = true; if (first) { first = false; printf("cs=%s BackgroundColour=%i\n", LColourSpaceToString(pDC->GetColourSpace()), BackgroundColour); for (int i=0; pal && i<3; i++) { auto *p = (*pal)[pixels[i]]; printf("px=%i, %i,%i,%i\n", pixels[i], p->r, p->g, p->b); } } */ switch (pDC->GetColourSpace()) { case CsIndex8: case CsAlpha8: { memcpy((*pDC)[lines], pixels, pDC->X()); break; } case CsBgr16: { LBgr16 *s = (LBgr16*) (*pDC)[lines]; LBgr16 *e = s + pDC->X(); GdcRGB *p = (*pal)[0], *pix; while (s < e) { pix = p + *pixels++; s->r = pix->r >> 3; s->g = pix->g >> 2; s->b = pix->b >> 3; s++; } break; } case CsRgb16: { LRgb16 *s = (LRgb16*) (*pDC)[lines]; LRgb16 *e = s + pDC->X(); GdcRGB *p = (*pal)[0], *pix; while (s < e) { pix = p + *pixels++; s->r = pix->r >> 3; s->g = pix->g >> 2; s->b = pix->b >> 3; s++; } break; } case System32BitColourSpace: { System32BitPixel *s = (System32BitPixel*) (*pDC)[lines]; System32BitPixel *e = s + pDC->X(); GdcRGB *p = (*pal)[0], *pix; if (Transparent) { while (s < e) { if (*pixels != BackgroundColour) { pix = p + *pixels; s->r = pix->r; s->g = pix->g; s->b = pix->b; s->a = 255; } else { s->r = 0; s->g = 0; s->b = 0; s->a = 0; } pixels++; s++; } } else // no transparent colour { while (s < e) { pix = p + *pixels++; s->r = pix->r; s->g = pix->g; s->b = pix->b; s->a = 255; s++; } } break; } default: { LAssert(!"Unsupported colour space"); break; } } ProcessedScanlines++; if (interlaced) { switch (pass) { case 0: lines += 8; if (lines >= pDC->Y()) { lines = 4; pass++; } break; case 1: lines += 8; if (lines >= pDC->Y()) { lines = 2; pass++; } break; case 2: lines += 4; if (lines >= pDC->Y()) { lines = 1; pass++; } break; case 3: lines += 2; break; } } else { lines++; } if (Meter) { int a = (int)Meter->Value() * 100 / pDC->Y(); int b = lines * 100 / pDC->Y(); if (abs(a-b) > 5) { Meter->Value(lines); } } return 0; } short GdcGif::init_exp(short size) { curr_size = size + 1; top_slot = 1 << curr_size; clearc = 1 << size; ending = clearc + 1; slot = newcodes = ending + 1; navail_bytes = nbits_left = 0; return 0; } short GdcGif::get_next_code() { short i, x; ulong ret; if (nbits_left == 0) { if (navail_bytes <= 0) { /* Out of bytes in current block, so read next block */ pbytes = byte_buff; if ((navail_bytes = get_byte()) < 0) { return(navail_bytes); } else if (navail_bytes) { for (i = 0; i < navail_bytes; ++i) { if ((x = get_byte()) < 0) { return(x); } byte_buff[i] = (uchar)x; } } } b1 = *pbytes++; nbits_left = 8; --navail_bytes; } ret = b1 >> (8 - nbits_left); while (curr_size > nbits_left) { if (navail_bytes <= 0) { /* Out of bytes in current block, so read next block */ pbytes = byte_buff; if ((navail_bytes = get_byte()) < 0) { return(navail_bytes); } else if (navail_bytes) { for (i = 0; i < navail_bytes; ++i) { if ((x = get_byte()) < 0) { return(x); } byte_buff[i] = (uchar)x; } } } b1 = *pbytes++; ret |= b1 << nbits_left; nbits_left += 8; --navail_bytes; } nbits_left -= curr_size; ret &= code_mask[curr_size]; return ((short) ret); } short GdcGif::decoder(int BitDepth, uchar interlaced) { uchar *sp, *bufptr; uchar *buf; short code, fc, oc, bufcnt; short c, size, ret; lines = 0; pass = 0; /* Initialize for decoding a new image... */ if ((size = get_byte()) < 0) { return(size); } if (size < 2 || 9 < size) { return(BAD_CODE_SIZE); } init_exp(size); /* Initialize in case they forgot to put in a clearc code. * (This shouldn't happen, but we'll try and decode it anyway...) */ oc = fc = 0; /* Allocate space for the decode buffer */ buf = new uchar[linewidth+1]; if (buf == NULL) { return (OUT_OF_MEMORY); } /* Set up the stack pointer and decode buffer pointer */ uchar *EndOfStack = stack + sizeof(stack); sp = stack; bufptr = buf; bufcnt = linewidth; /* This is the main loop. For each code we get we pass through the * linked list of prefix codes, pushing the corresponding "character" for * each code onto the stack. When the list reaches a single "character" * we push that on the stack too, and then start unstacking each * character for output in the correct order. Special handling is * included for the clearc code, and the whole thing ends when we get * an ending code. */ while ((c = get_next_code()) != ending) { /* If we had a file error, return without completing the decode */ if (c < 0) { DeleteArray(buf); return(0); } /* If the code is a clearc code, reinitialize all necessary items. */ if (c == clearc) { curr_size = size + 1; slot = newcodes; top_slot = 1 << curr_size; /* Continue reading codes until we get a non-clearc code * (Another unlikely, but possible case...) */ while ((c = get_next_code()) == clearc) ; /* If we get an ending code immediately after a clearc code * (Yet another unlikely case), then break out of the loop. */ if (c == ending) { break; } /* Finally, if the code is beyond the range of already set codes, * (This one had better !happen... I have no idea what will * result from this, but I doubt it will look good...) then set it * to color zero. */ if (c >= slot) { c = 0; } oc = fc = c; /* And let us not forget to put the char into the buffer... And * if, on the off chance, we were exactly one pixel from the end * of the line, we have to send the buffer to the out_line() * routine... */ *bufptr++ = (uchar)c; if (--bufcnt == 0) { if ((ret = out_line(buf, linewidth, interlaced, BitDepth)) < 0) { DeleteArray(buf); return (ret); } bufptr = buf; bufcnt = linewidth; } } else { /* In this case, it's not a clearc code or an ending code, so * it must be a code code... So we can now decode the code into * a stack of character codes. (Clear as mud, right?) */ code = c; /* Here we go again with one of those off chances... If, on the * off chance, the code we got is beyond the range of those already * set up (Another thing which had better !happen...) we trick * the decoder into thinking it actually got the last code read. * (Hmmn... I'm not sure why this works... But it does...) */ if (code >= slot) { if (code > slot) { ++bad_code_count; } code = oc; *sp++ = (uchar)fc; } /* Here we scan back along the linked list of prefixes, pushing * helpless characters (ie. suffixes) onto the stack as we do so. */ while (code >= newcodes) { if (sp >= EndOfStack || code >= MAX_CODES + 1) { return -1; } *sp++ = suffix[code]; code = prefix[code]; } /* Push the last character on the stack, and set up the new * prefix and suffix, and if the required slot number is greater * than that allowed by the current bit size, increase the bit * size. (NOTE - If we are all full, we *don't* save the new * suffix and prefix... I'm not certain if this is correct... * it might be more proper to overwrite the last code... */ *sp++ = (uchar)code; if (slot < top_slot) { suffix[slot] = (uchar)(fc = code); prefix[slot++] = oc; oc = c; } if (slot >= top_slot) { if (curr_size < 12) { top_slot <<= 1; ++curr_size; } } /* Now that we've pushed the decoded string (in reverse order) * onto the stack, lets pop it off and put it into our decode * buffer... And when the decode buffer is full, write another * line... */ while (sp > stack) { *bufptr++ = *(--sp); if (--bufcnt == 0) { if ((ret = out_line(buf, linewidth, interlaced, BitDepth)) < 0) { DeleteArray(buf); return(ret); } bufptr = buf; bufcnt = linewidth; } } } } ret = 0; if (bufcnt != linewidth) { ret = out_line(buf, (linewidth - bufcnt), interlaced, BitDepth); } DeleteArray(buf); return(ret); } union LogicalScreenBits { uint8_t u8; struct { uint8_t TableSize : 3; uint8_t SortFlag : 1; uint8_t ColourRes : 3; uint8_t GlobalColorTable : 1; }; }; union LocalColourBits { uint8_t u8; struct { uint8_t TableBits : 3; uint8_t Reserved : 2; uint8_t SortFlag : 1; uint8_t Interlaced : 1; uint8_t LocalColorTable : 1; }; }; union GfxCtrlExtBits { uint8_t u8; struct { uint8_t Transparent : 1; uint8_t UserInput : 1; uint8_t DisposalMethod : 3; uint8_t Reserved : 3; }; }; bool GifLoadPalette(LStream *s, LSurface *pDC, int TableBits) { LRgb24 Rgb[256]; int Colours = 1 << (TableBits + 1); int Bytes = Colours * sizeof(Rgb[0]); memset(Rgb, 0xFF, sizeof(Rgb)); if (s->Read(Rgb, Bytes) != Bytes) return false; LPalette *Pal = new LPalette((uint8_t*)Rgb, 256); if (!Pal) return false; pDC->Palette(Pal); return true; } LFilter::IoStatus GdcGif::ReadImage(LSurface *pdc, LStream *in) { LFilter::IoStatus Status = IoError; pDC = pdc; s = in; ProcessedScanlines = 0; if (pDC && s) { bad_code_count = 0; if (!FindHeader(0, "GIF8?a", s)) { // not a gif file } else { Transparent = false; LogicalScreenBits LogBits; uchar interlace = false; uint16 LogicalX = 0; uint16 LogicalY = 0; BackgroundColour = 0; uint8_t PixelAspectRatio = 0; // read header Read(s, &LogicalX, sizeof(LogicalX)); Read(s, &LogicalY, sizeof(LogicalY)); Read(s, &LogBits.u8, sizeof(LogBits.u8)); int Bits = LogBits.ColourRes + 1; Read(s, &BackgroundColour, sizeof(BackgroundColour)); Read(s, &PixelAspectRatio, sizeof(PixelAspectRatio)); if (LogBits.GlobalColorTable) { GifLoadPalette(s, pDC, LogBits.TableSize); } // Start reading the block stream bool Done = false; uchar BlockCode = 0; uchar BlockLabel = 0; uchar BlockSize = 0; while (!Done) { #define Rd(Var) \ if (!Read(s, &Var, sizeof(Var))) \ { \ Done = true; \ LgiTrace("%s:%i - Failed to read %i (" LPrintfInt64 " of " LPrintfInt64 ")\n", \ _FL, (int)sizeof(Var), in->GetPos(), in->GetSize()); \ break; \ } Rd(BlockCode); switch (BlockCode) { case 0x2C: { // Image Descriptor uint16 x1, y1, sx, sy; LocalColourBits LocalBits; Rd(x1); Rd(y1); Rd(sx); Rd(sy); Rd(LocalBits.u8); linewidth = sx; interlace = LocalBits.Interlaced != 0; if (pDC->Create(sx, sy, CsIndex8)) { if (LocalBits.LocalColorTable) { GifLoadPalette(s, pDC, LocalBits.TableBits); } // Progress if (Meter) { Meter->SetDescription("scanlines"); Meter->SetRange(sy); } // Decode image decoder(Bits, interlace); if (ProcessedScanlines == pDC->Y()) Status = IoSuccess; if (Transparent && !LColourSpaceHasAlpha(pDC->GetColourSpace())) { // Setup alpha channel pDC->HasAlpha(true); LSurface *Alpha = pDC->AlphaDC(); if (Alpha) { for (int y=0; yY(); y++) { uchar *C = (*pDC)[y]; uchar *A = (*Alpha)[y]; for (int x=0; xX(); x++) A[x] = C[x] == BackgroundColour ? 0x00 : 0xff; } } } } else { LgiTrace("%s:%i - Failed to create output surface.\n", _FL); } Done = true; break; } case 0x21: { uint8_t GraphicControlLabel; uint8_t BlockSize; GfxCtrlExtBits ExtBits; uint16 Delay; Rd(GraphicControlLabel); Rd(BlockSize); switch (GraphicControlLabel) { case 0xF9: { Rd(ExtBits.u8); Rd(Delay); Rd(BackgroundColour); Transparent = ExtBits.Transparent != 0; break; } default: { s->SetPos(s->GetPos() + BlockSize); break; } } Rd(BlockSize); while (BlockSize) { int64 NewPos = s->GetPos() + BlockSize; if (s->SetPos(NewPos) != NewPos || !Read(s, &BlockSize, sizeof(BlockSize))) break; } break; } default: { // unknown block Rd(BlockLabel); Rd(BlockSize); while (BlockSize) { int64 NewPos = s->GetPos() + BlockSize; if (s->SetPos(NewPos) != NewPos || !Read(s, &BlockSize, sizeof(BlockSize))) break; } break; } } } } } return Status; } LFilter::IoStatus GdcGif::WriteImage(LStream *Out, LSurface *pDC) { LVariant Transparent; int Back = -1; LVariant v; if (!Out || !pDC) return LFilter::IoError; if (pDC->GetBits() > 8) { if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "The GIF format only supports 1 to 8 bit graphics."); return LFilter::IoUnsupportedFormat; } #ifdef FILTER_UI LVariant Parent; if (Props) { Props->GetValue(LGI_FILTER_PARENT_WND, Parent); if (Props->GetValue(LGI_FILTER_BACKGROUND, v)) { Back = v.CastInt32(); } if (Parent.Type == GV_GVIEW) { // If the source document has an alpha channel then we use // that to create transparent pixels in the output, otherwise // we ask the user if they want the background transparent... if (pDC->AlphaDC()) { Transparent = true; // However we have to pick an unused colour to set as the // "background" pixel value bool Used[256]; ZeroObj(Used); for (int y=0; yY(); y++) { uint8_t *p = (*pDC)[y]; uint8_t *a = (*pDC->AlphaDC())[y]; LAssert(p && a); if (!p || !a) break; uint8_t *e = p + pDC->X(); while (p < e) { if (*a) Used[*p] = true; a++; p++; } } Back = -1; for (int i=0; i<256; i++) { if (!Used[i]) { Back = i; break; } } if (Back < 0) { if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "No unused colour for transparent pixels??"); return IoError; } } else { + LAssert(!"Move this to the parent app."); + + /* // put up a dialog to ask about transparent colour LTransparentDlg Dlg((LView*)Parent.Value.Ptr, &Transparent); if (!Dlg.DoModal()) { Props->SetValue("Cancel", v = 1); return IoCancel; } + */ + + return IoCancel; } if (Transparent.CastInt32() && Back < 0) { LAssert(!"No background colour available??"); if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "Transparency requested, but no background colour set."); return IoError; } } } #endif LPalette *Pal = pDC->Palette(); // Intel byte ordering Out->SetSize(0); // Header Out->Write((void*)"GIF89a", 6); // Logical screen descriptor int16 s = pDC->X(); Write(Out, &s, sizeof(s)); s = pDC->Y(); Write(Out, &s, sizeof(s)); bool Ordered = false; uint8_t c = ((Pal != 0) ? 0x80 : 0) | // global colour table/transparent (pDC->GetBits() - 1) | // bits per pixel ((Ordered) ? 0x08 : 0) | // colours are sorted (pDC->GetBits() - 1); Out->Write(&c, 1); c = 0; Out->Write(&c, 1); // background colour c = 0; Out->Write(&c, 1); // aspect ratio // global colour table if (Pal) { uchar Buf[768]; uchar *d = Buf; int Colours = 1 << pDC->GetBits(); for (int i=0; ir; *d++ = s->g; *d++ = s->b; } else { *d++ = i; *d++ = i; *d++ = i; } } Out->Write(Buf, Colours * 3); } if (Transparent.CastInt32()) { // Graphic Control Extension uchar gce[] = {0x21, 0xF9, 4, 1, 0, 0, (uchar)Back, 0 }; Out->Write(gce, sizeof(gce)); } // Image descriptor c = 0x2c; Out->Write(&c, 1); // Image Separator s = 0; Write(Out, &s, sizeof(s)); // Image left position s = 0; Write(Out, &s, sizeof(s)); // Image top position s = pDC->X(); Write(Out, &s, sizeof(s)); // Image width s = pDC->Y(); Write(Out, &s, sizeof(s)); // Image height c = 0; Out->Write(&c, 1); // Flags // Image data c = 8; Out->Write(&c, 1); // Min code size LMemQueue Encode, Pixels; // Get input ready int Len = (pDC->X() * pDC->GetBits() + 7) / 8; uint8_t *buf = pDC->AlphaDC() ? new uint8_t[Len] : 0; for (int y=0; yY(); y++) { uint8_t *p = (*pDC)[y]; if (!p) continue; if (pDC->AlphaDC()) { // Preprocess pixels to make the alpha channel into the // transparent colour. uint8_t *a = (*pDC->AlphaDC())[y]; uint8_t *e = p + pDC->X(); uint8_t *o = buf; while (p < e) { if (*a++) *o++ = *p; else *o++ = Back; p++; } LAssert(o == buf + Len); p = buf; } Pixels.Write(p, Len); } DeleteArray(buf); // Compress Lzw Encoder; Encoder.Meter = Meter; if (Encoder.Compress(&Encode, &Pixels)) { uchar Buf[256]; // write data out while ((Len = (int)Encode.GetSize()) > 0) { int l = MIN(Len, 255); if (Encode.Read(Buf, l)) { c = l; Out->Write(&c, 1); // Sub block size Out->Write(Buf, l); } } c = 0; Out->Write(&c, 1); // Terminator sub block } // Trailer c = 0x3b; Out->Write(&c, 1); return LFilter::IoSuccess; } GdcGif::GdcGif() { ProcessedScanlines = 0; } diff --git a/src/common/Gdc2/Filters/Png.cpp b/src/common/Gdc2/Filters/Png.cpp --- a/src/common/Gdc2/Filters/Png.cpp +++ b/src/common/Gdc2/Filters/Png.cpp @@ -1,1597 +1,1602 @@ /*hdr ** FILE: Png.cpp ** AUTHOR: Matthew Allen ** DATE: 8/9/1998 ** DESCRIPTION: Png file filter ** ** Copyright (C) 2002, Matthew Allen ** fret@memecode.com */ // // 'png.h' comes from libpng, which you can get from: // http://www.libpng.org/pub/png/libpng.html // // You will also need zlib, which pnglib requires. zlib // is available here: // http://www.gzip.org/zlib // // If you don't want to build with PNG support then set // the define HAS_LIBPNG_ZLIB to '0' in Lgi.h // #ifndef __CYGWIN__ #include "math.h" #include "png.h" #endif #include "lgi/common/Lgi.h" #include "lgi/common/Palette.h" #ifdef __CYGWIN__ #include "png.h" #endif #include #include #include #include "lgi/common/LibraryUtils.h" #ifdef FILTER_UI #include "TransparentDlg.h" #endif #include "lgi/common/Variant.h" // Pixel formats typedef uint8_t Png8; typedef LRgb24 Png24; typedef LRgba32 Png32; typedef LRgb48 Png48; typedef LRgba64 Png64; #if PNG_LIBPNG_VER_MAJOR <= 1 && PNG_LIBPNG_VER_MINOR <= 2 #define png_const_infop png_infop #define png_const_bytep png_bytep #endif #ifdef LINUX const char *LinuxLibName() { static char lib[64]; sprintf_s(lib, sizeof(lib), "libpng%i", PNG_LIBPNG_VER_SONUM); // printf("png lib name = '%s'\n", lib); return lib; } #endif #if LIBPNG_SHARED #define LIBPNG Lib-> const char *sLibrary = #if defined(MAC) || defined(HAIKU) "libpng16" #elif defined(LINUX) LinuxLibName() #else #if defined(__CYGWIN__) "cygpng12" #else "libpng16" #ifdef _MSC_VER_STR "_" #if _MSC_VER >= _MSC_VER_VS2019 _MSC_YEAR_STR #else _MSC_VER_STR #endif #if defined(LGI_64BIT) "x64" #else "x32" #endif #ifdef _DEBUG "d" #endif #endif #endif #endif ; // Library interface class LibPng : public LLibrary { public: LibPng() : LLibrary(sLibrary) { static bool First = true; if (First) { First = false; auto Loaded = IsLoaded(); if (Loaded) { LgiTrace("%s:%i - PNG: %s\n", _FL, GetFullPath().Get()); } else { #if defined(WINDOWS) && defined(_DEBUG) auto ReleaseLib = LString(sLibrary)(0, -2); if (!Load(ReleaseLib)) #endif LgiTrace("%s:%i - Failed to load '%s'.\n", _FL, sLibrary); } } } DynFunc4( png_structp, png_create_read_struct, png_const_charp, user_png_ver, png_voidp, error_ptr, png_error_ptr, error_fn, png_error_ptr, warn_fn); DynFunc4( png_structp, png_create_write_struct, png_const_charp, user_png_ver, png_voidp, error_ptr, png_error_ptr, error_fn, png_error_ptr, warn_fn); DynFunc1( png_infop, png_create_info_struct, png_structp, png_ptr); DynFunc2( int, png_destroy_info_struct, png_structp, png_ptr, png_infopp, info_ptr); DynFunc3( int, png_destroy_read_struct, png_structpp, png_ptr_ptr, png_infopp, info_ptr_ptr, png_infopp, end_info_ptr_ptr); DynFunc2( int, png_destroy_write_struct, png_structpp, png_ptr_ptr, png_infopp, info_ptr_ptr); DynFunc3( int, png_set_read_fn, png_structp, png_ptr, png_voidp, io_ptr, png_rw_ptr, read_data_fn); DynFunc4( int, png_set_write_fn, png_structp, png_ptr, png_voidp, io_ptr, png_rw_ptr, write_data_fn, png_flush_ptr, output_flush_fn); DynFunc4( int, png_read_png, png_structp, png_ptr, png_infop, info_ptr, int, transforms, png_voidp, params); DynFunc4( int, png_write_png, png_structp, png_ptr, png_infop, info_ptr, int, transforms, png_voidp, params); DynFunc2( png_bytepp, png_get_rows, png_structp, png_ptr, png_infop, info_ptr); DynFunc3( int, png_set_rows, png_structp, png_ptr, png_infop, info_ptr, png_bytepp, row_pointers); DynFunc6( png_uint_32, png_get_iCCP, png_structp, png_ptr, png_const_infop, info_ptr, png_charpp, name, int*, compression_type, png_bytepp, profile, png_uint_32*, proflen); DynFunc6( int, png_set_iCCP, png_structp, png_ptr, png_infop, info_ptr, png_charp, name, int, compression_type, png_const_bytep, profile, png_uint_32, proflen); DynFunc5( png_uint_32, png_get_tRNS, png_structp, png_ptr, png_infop, info_ptr, png_bytep*, trans_alpha, int*, num_trans, png_color_16p*, trans_color); DynFunc3( png_uint_32, png_get_valid, png_structp, png_ptr, png_infop, info_ptr, png_uint_32, flag); DynFunc4( png_uint_32, png_get_PLTE, png_structp, png_ptr, png_infop, info_ptr, png_colorp*, palette, int*, num_palette); DynFunc2( png_uint_32, png_get_image_width, png_structp, png_ptr, png_infop, info_ptr); DynFunc2( png_uint_32, png_get_image_height, png_structp, png_ptr, png_infop, info_ptr); DynFunc2( png_byte, png_get_channels, png_structp, png_ptr, png_infop, info_ptr); #if 1 // PNG_LIBPNG_VER <= 10250 DynFunc2( png_byte, png_get_color_type, png_structp, png_ptr, png_infop, info_ptr); #else DynFunc2( png_byte, png_get_color_type, png_const_structp, png_ptr, png_const_infop, info_ptr); #endif DynFunc2( png_byte, png_get_bit_depth, png_structp, png_ptr, png_infop, info_ptr); DynFunc1( png_voidp, png_get_error_ptr, png_structp, png_ptr); DynFunc1( png_voidp, png_get_io_ptr, png_structp, png_ptr); DynFunc9( int, png_set_IHDR, png_structp, png_ptr, png_infop, info_ptr, png_uint_32, width, png_uint_32, height, int, bit_depth, int, color_type, int, interlace_method, int, compression_method, int, filter_method); DynFunc4( int, png_set_PLTE, png_structp, png_ptr, png_infop, info_ptr, png_colorp, palette, int, num_palette); DynFunc5( int, png_set_tRNS, png_structp, png_ptr, png_infop, info_ptr, png_bytep, trans_alpha, int, num_trans, png_color_16p, trans_color); /* DynFunc2( png_byte, png_get_interlace_type, png_const_structp, png_ptr, png_const_infop, info_ptr); */ }; class InitLibPng : public LMutex { LAutoPtr Png; public: LibPng *Get() { if (Lock(_FL)) { if (!Png) Png.Reset(new LibPng); Unlock(); } return Png; } InitLibPng() : LMutex("InitLibPng") { } } CurrentLibPng; #else #define LIBPNG #endif class GdcPng : public LFilter { static char PngSig[]; friend void PNGAPI LibPngError(png_structp Png, png_const_charp Msg); friend void PNGAPI LibPngWarning(png_structp Png, png_const_charp Msg); #if LIBPNG_SHARED LibPng *Lib; #endif int Pos; uchar *PrevScanLine; LSurface *pDC; LMemQueue DataPipe; LView *Parent; jmp_buf Here; public: GdcPng ( #if LIBPNG_SHARED LibPng *lib #endif ); ~GdcPng(); const char *GetComponentName() override { return "libpng"; } Format GetFormat() override { return FmtPng; } void SetMeter(int i) { if (Meter) Meter->Value(i); } int GetCapabilites() override { return FILTER_CAP_READ | FILTER_CAP_WRITE; } IoStatus ReadImage(LSurface *pDC, LStream *In) override; IoStatus WriteImage(LStream *Out, LSurface *pDC) override; bool GetVariant(const char *n, LVariant &v, const char *a = NULL) override { if (!_stricmp(n, LGI_FILTER_TYPE)) { v = "Png"; // Portable Network Graphic } else if (!_stricmp(n, LGI_FILTER_EXTENSIONS)) { v = "PNG"; } else return false; return true; } }; // Object Factory class GdcPngFactory : public LFilterFactory { bool CheckFile(const char *File, int Access, const uchar *Hint) { if (Hint) { return Hint[1] == 'P' && Hint[2] == 'N' && Hint[3] == 'G'; } else { return (File) ? stristr(File, ".png") != 0 : false; } } LFilter *NewObject() { return new GdcPng ( #if LIBPNG_SHARED CurrentLibPng.Get() #endif ); } } PngFactory; // Class impl char GdcPng::PngSig[] = { (char)137, 'P', 'N', 'G', '\r', '\n', (char)26, '\n', 0 }; GdcPng::GdcPng( #if LIBPNG_SHARED LibPng *lib #endif ) { #if LIBPNG_SHARED Lib = lib; #endif Parent = 0; Pos = 0; PrevScanLine = 0; } GdcPng::~GdcPng() { DeleteArray(PrevScanLine); } void PNGAPI LibPngError(png_structp Png, png_const_charp Msg) { GdcPng *This = (GdcPng*) #if LIBPNG_SHARED CurrentLibPng.Get()-> #endif png_get_error_ptr(Png); if (This) { printf("Libpng Error Message='%s'\n", Msg); if (This->Props) { LVariant v; This->Props->SetValue(LGI_FILTER_ERROR, v = (char*)Msg); } longjmp(This->Here, -1); } } void PNGAPI LibPngWarning(png_structp Png, png_const_charp Msg) { LgiTrace("LibPng Warning: %s\n", Msg); } void PNGAPI LibPngRead(png_structp Png, png_bytep Ptr, png_size_t Size) { LStream *s = (LStream*) #if LIBPNG_SHARED CurrentLibPng.Get()-> #endif png_get_io_ptr(Png); if (s) { s->Read(Ptr, Size); } else { LgiTrace("%s:%i - No this ptr? (%p)\n", __FILE__, __LINE__, Ptr); LAssert(0); } } struct PngWriteInfo { LStream *s; Progress *m; }; void PNGAPI LibPngWrite(png_structp Png, png_bytep Ptr, png_size_t Size) { PngWriteInfo *i = (PngWriteInfo*) #if LIBPNG_SHARED CurrentLibPng.Get()-> #endif png_get_io_ptr(Png); if (i) { i->s->Write(Ptr, Size); /* if (i->m) i->m->Value(Png->flush_rows); */ } else { LgiTrace("%s:%i - No this ptr?\n", __FILE__, __LINE__); LAssert(0); } } template void Read32_16(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 3; o->g = i->g >> 2; o->b = i->b >> 3; o++; i++; } } template void Read64_16(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 11; o->g = i->g >> 10; o->b = i->b >> 11; o++; i++; } } template void Read32_24(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r; o->g = i->g; o->b = i->b; o++; i++; } } template void Read64_24(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 8; o->g = i->g >> 8; o->b = i->b >> 8; o++; i++; } } template void Read32_32(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r; o->g = i->g; o->b = i->b; o->a = 255; o++; i++; } } template void Read64_32(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 8; o->g = i->g >> 8; o->b = i->b >> 8; o->a = 255; o++; i++; } } template void ReadAlpha32_16(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 3; o->g = i->g >> 2; o->b = i->b >> 3; o++; i++; } } template void ReadAlpha64_16(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 11; o->g = i->g >> 10; o->b = i->b >> 11; o++; i++; } } template void ReadAlpha32_24(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r; o->g = i->g; o->b = i->b; o++; i++; } } template void ReadAlpha64_24(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 8; o->g = i->g >> 8; o->b = i->b >> 8; o++; i++; } } template void ReadAlpha32_32(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r; o->g = i->g; o->b = i->b; o->a = i->a; o++; i++; } } template void ReadAlpha64_32(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 8; o->g = i->g >> 8; o->b = i->b >> 8; o->a = i->a >> 8; o++; i++; } } LFilter::IoStatus GdcPng::ReadImage(LSurface *pDeviceContext, LStream *In) { LFilter::IoStatus Status = IoError; Pos = 0; pDC = pDeviceContext; DeleteArray(PrevScanLine); if (!pDC) { LAssert(!"No DC."); return Status; } LVariant v; if (Props && Props->GetValue(LGI_FILTER_PARENT_WND, v) && v.Type == GV_GVIEW) { Parent = (LView*)v.Value.Ptr; } #if LIBPNG_SHARED if (!Lib->IsLoaded() && !Lib->Load(sLibrary)) { LString s; s.Printf("libpng is missing (%s.%s)", sLibrary, LGI_LIBRARY_EXT); if (Props) Props->SetValue(LGI_FILTER_ERROR, v = s); else LgiTrace("%s:%i - %s\n", _FL, s.Get()); static bool Warn = true; if (Warn) { LgiTrace("%s:%i - Unable to load libpng (%s.%s).\n", _FL, sLibrary, LGI_LIBRARY_EXT); Warn = false; } return LFilter::IoComponentMissing; } #endif png_structp png_ptr = NULL; if (setjmp(Here)) { return Status; } png_ptr = LIBPNG png_create_read_struct(PNG_LIBPNG_VER_STRING, (void*)this, LibPngError, LibPngWarning); if (!png_ptr) { if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "png_create_read_struct failed."); } else { png_infop info_ptr = LIBPNG png_create_info_struct(png_ptr); if (info_ptr) { LIBPNG png_set_read_fn(png_ptr, In, LibPngRead); #if 0 // What was this for again? int off = (char*)&png_ptr->io_ptr - (char*)png_ptr; if (!png_ptr->io_ptr) { printf("io_ptr offset = %i\n", off); LAssert(0); CurrentLibPng = 0; return false; } #endif LIBPNG png_read_png(png_ptr, info_ptr, 0, 0); png_bytepp Scan0 = LIBPNG png_get_rows(png_ptr, info_ptr); if (Scan0) { int BitDepth = LIBPNG png_get_bit_depth(png_ptr, info_ptr); int FinalBits = BitDepth == 16 ? 8 : BitDepth; int ColourType = LIBPNG png_get_color_type(png_ptr, info_ptr); int Channels = LIBPNG png_get_channels(png_ptr, info_ptr); int RequestBits = FinalBits * Channels; LColourSpace InCs = ColourType == PNG_COLOR_TYPE_GRAY_ALPHA ? CsIndex8 : LBitsToColourSpace(MAX(RequestBits, 8)); if (!pDC->Create( LIBPNG png_get_image_width(png_ptr, info_ptr), LIBPNG png_get_image_height(png_ptr, info_ptr), InCs, LSurface::SurfaceRequireExactCs)) { printf("%s:%i - LMemDC::Create(%i, %i, %i) failed.\n", _FL, LIBPNG png_get_image_width(png_ptr, info_ptr), LIBPNG png_get_image_height(png_ptr, info_ptr), RequestBits); } else { bool Error = false; #if 1 if (ColourType == PNG_COLOR_TYPE_GRAY_ALPHA) { pDC->HasAlpha(true); // Setup alpha channel } /* printf("PngRead %s->%s\n", LColourSpaceToString(InCs), LColourSpaceToString(pDC->GetColourSpace())); */ #endif // Copy in the scanlines int ActualBits = pDC->GetBits(); int ScanLen = LIBPNG png_get_image_width(png_ptr, info_ptr) * ActualBits / 8; LColourSpace OutCs = pDC->GetColourSpace(); for (int y=0; yY() && !Error; y++) { uchar *Scan = (*pDC)[y]; LAssert(Scan != NULL); switch (RequestBits) { case 1: { uchar *o = Scan; uchar *e = Scan + pDC->X(); uchar *i = Scan0[y]; uchar Mask = 0x80; while (o < e) { *o++ = (*i & Mask) ? 1 : 0; Mask >>= 1; if (!Mask) { i++; Mask = 0x80; } } break; } case 2: { uchar *i = Scan0[y]; uchar *o = Scan; for (int x=0; xX(); x++) { switch (x & 3) { case 0: *o++ = (*i >> 6) & 0x3; break; case 1: *o++ = (*i >> 4) & 0x3; break; case 2: *o++ = (*i >> 2) & 0x3; break; case 3: *o++ = (*i++ >> 0) & 0x3; break; } } break; } case 4: { uchar *i = Scan0[y]; uchar *o = Scan; for (int x=0; xX(); x++) { if (x & 1) *o++ = *i++ & 0xf; else *o++ = (*i >> 4) & 0xf; } break; } case 8: { memcpy(Scan, Scan0[y], ScanLen); break; } case 16: { if (ColourType == PNG_COLOR_TYPE_GRAY_ALPHA) { uint8_t *grey = Scan; uint8_t *alpha = (*(pDC->AlphaDC()))[y]; LAssert(grey && alpha); uint8_t *end = grey + pDC->X(); uint8_t *in = Scan0[y]; while (grey < end) { *grey++ = *in++; *alpha++ = *in++; } } else { memcpy(Scan, Scan0[y], ScanLen); } break; } case 24: { switch (OutCs) { #define Read24Case(name, bits) \ case Cs##name: \ { \ if (LIBPNG png_get_bit_depth(png_ptr, info_ptr) == 16) \ Read64_##bits((L##name*)Scan, (Png48*)Scan0[y], pDC->X()); \ else \ Read32_##bits((L##name*)Scan, (Png24*)Scan0[y], pDC->X()); \ break; \ } Read24Case(Rgb16, 16); Read24Case(Bgr16, 16); Read24Case(Rgb24, 24); Read24Case(Bgr24, 24); Read24Case(Xrgb32, 24); Read24Case(Rgbx32, 24); Read24Case(Xbgr32, 24); Read24Case(Bgrx32, 24); Read24Case(Rgba32, 32); Read24Case(Bgra32, 32); Read24Case(Argb32, 32); Read24Case(Abgr32, 32); default: LgiTrace("%s:%i - Unsupported colour space: 0x%x (%s)\n", _FL, pDC->GetColourSpace(), LColourSpaceToString(pDC->GetColourSpace())); LAssert(!"Not impl."); break; } break; } case 32: { switch (pDC->GetColourSpace()) { #define Read32Case(name, bits) \ case Cs##name: \ { \ if (LIBPNG png_get_bit_depth(png_ptr, info_ptr) == 16) \ ReadAlpha64_##bits((L##name*)Scan, (Png64*)Scan0[y], pDC->X()); \ else \ ReadAlpha32_##bits((L##name*)Scan, (Png32*)Scan0[y], pDC->X()); \ break; \ } Read32Case(Rgb16, 16); Read32Case(Bgr16, 16); Read32Case(Rgb24, 24); Read32Case(Bgr24, 24); Read32Case(Xrgb32, 24); Read32Case(Rgbx32, 24); Read32Case(Xbgr32, 24); Read32Case(Bgrx32, 24); Read32Case(Rgba32, 32); Read32Case(Bgra32, 32); Read32Case(Argb32, 32); Read32Case(Abgr32, 32); default: LgiTrace("%s:%i - Unsupported colour space: 0x%x (%s)\n", _FL, pDC->GetColourSpace(), LColourSpaceToString(pDC->GetColourSpace())); LAssert(!"Not impl."); if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "Missing scan convertor"); Error = true; break; } break; } default: { if (ActualBits == RequestBits) { memcpy(Scan, Scan0[y], ScanLen); } else { LAssert(!"Yeah you need to impl a convertor here."); if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "Missing scan convertor"); Error = true; } break; } } } if (RequestBits == 32) { // bool IsPreMul = pDC->IsPreMultipliedAlpha(); pDC->ConvertPreMulAlpha(true); } if (ActualBits <= 8) { // Copy in the palette png_colorp pal; int num_pal = 0; if (LIBPNG png_get_PLTE(png_ptr, info_ptr, &pal, &num_pal) == PNG_INFO_PLTE) { LPalette *Pal = new LPalette(0, num_pal); if (Pal) { for (int i=0; ir = pal[i].red; Rgb->g = pal[i].green; Rgb->b = pal[i].blue; } } pDC->Palette(Pal, true); } } if (LIBPNG png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { png_bytep trans_alpha = NULL; png_color_16p trans_color; int num_trans; if (LIBPNG png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, &trans_color)) { pDC->HasAlpha(true); LSurface *Alpha = pDC->AlphaDC(); if (Alpha) { if (trans_alpha) { for (int y=0; yY(); y++) { uchar *a = (*Alpha)[y]; uchar *p = (*pDC)[y]; for (int x=0; xX(); x++) { if (p[x] < num_trans) { a[x] = trans_alpha[p[x]]; } else { a[x] = 0xff; } } } } else if (trans_color) { for (int y=0; yY(); y++) { uchar *a = (*Alpha)[y]; uchar *p = (*pDC)[y]; for (int x=0; xX(); x++) { a[x] = p[x] == trans_color->index ? 0x00 : 0xff; } } } } else { printf("%s:%i - No alpha channel.\n", _FL); } } else { printf("%s:%i - Bad trans ptr.\n", _FL); } } } Status = Error ? IoError : IoSuccess; } } else LgiTrace("%s:%i - png_get_rows failed.\n", _FL); LIBPNG png_destroy_info_struct(png_ptr, &info_ptr); } png_charp ProfName = 0; int CompressionType = 0; png_bytep ColProf = 0; png_uint_32 ColProfLen = 0; if (LIBPNG png_get_iCCP(png_ptr, info_ptr, &ProfName, &CompressionType, &ColProf, &ColProfLen) && Props) { v.SetBinary(ColProfLen, ColProf); Props->SetValue(LGI_FILTER_COLOUR_PROF, v); } LIBPNG png_destroy_read_struct(&png_ptr, 0, 0); } return Status; } LFilter::IoStatus GdcPng::WriteImage(LStream *Out, LSurface *pDC) { LFilter::IoStatus Status = IoError; LVariant Transparent; bool HasTransparency = false; COLOUR Back = 0; LVariant v; if (!pDC) return LFilter::IoError; #if LIBPNG_SHARED if (!Lib->IsLoaded() && !Lib->Load(sLibrary)) { static bool Warn = true; if (Warn) { LgiTrace("%s:%i - Unabled to load libpng.\n", _FL); Warn = false; } return LFilter::IoComponentMissing; } #endif // Work out whether the image has transparency if (pDC->GetBits() == 32) { // Check alpha channel for (int y=0; yY() && !HasTransparency; y++) { System32BitPixel *p = (System32BitPixel*)(*pDC)[y]; if (!p) break; System32BitPixel *e = p + pDC->X(); while (p < e) { if (p->a < 255) { HasTransparency = true; break; } p++; } } } else if (pDC->AlphaDC()) { LSurface *a = pDC->AlphaDC(); if (a) { for (int y=0; yY() && !HasTransparency; y++) { uint8_t *p = (*a)[y]; if (!p) break; uint8_t *e = p + a->X(); while (p < e) { if (*p < 255) { HasTransparency = true; break; } p++; } } } } if (Props) { if (Props->GetValue(LGI_FILTER_PARENT_WND, v) && v.Type == GV_GVIEW) { Parent = (LView*)v.Value.Ptr; } if (Props->GetValue(LGI_FILTER_BACKGROUND, v)) { Back = v.CastInt32(); } Props->GetValue(LGI_FILTER_TRANSPARENT, Transparent); } #ifdef FILTER_UI if (Parent && Transparent.IsNull()) { + LAssert(!"Move this to the parent app."); + // put up a dialog to ask about transparent colour - LTransparentDlg Dlg(Parent, &Transparent); - if (!Dlg.DoModal()) + auto Dlg = new LTransparentDlg(Parent, &Transparent); + Dlg->DoModal([this, Dlg](auto dlg, auto code) { - if (Props) + LVariant v; + + if (!code && Props) Props->SetValue("Cancel", v = 1); + delete Dlg; return IoCancel; - } + }); } #endif if (setjmp(Here) == 0 && pDC && Out) { LVariant ColProfile; if (Props) { Props->GetValue(LGI_FILTER_COLOUR_PROF, ColProfile); } // setup png_structp png_ptr = LIBPNG png_create_write_struct( PNG_LIBPNG_VER_STRING, (void*)this, LibPngError, LibPngWarning); if (png_ptr) { png_infop info_ptr = LIBPNG png_create_info_struct(png_ptr); if (info_ptr) { Out->SetSize(0); PngWriteInfo WriteInfo; WriteInfo.s = Out; WriteInfo.m = Meter; LIBPNG png_set_write_fn(png_ptr, &WriteInfo, LibPngWrite, 0); // png_set_write_status_fn(png_ptr, write_row_callback); bool KeyAlpha = false; bool ChannelAlpha = false; LMemDC *pTemp = 0; if (pDC->AlphaDC() && HasTransparency) { pTemp = new LMemDC(pDC->X(), pDC->Y(), System32BitColourSpace); if (pTemp) { pTemp->Colour(0); pTemp->Rectangle(); pTemp->Op(GDC_ALPHA); pTemp->Blt(0, 0, pDC); pTemp->Op(GDC_SET); pDC = pTemp; ChannelAlpha = true; } } else { if (Transparent.CastInt32() && Props && Props->GetValue(LGI_FILTER_BACKGROUND, v)) { KeyAlpha = true; } } int Ar = R32(Back); int Ag = G32(Back); int Ab = B32(Back); if (pDC->GetBits() == 32) { if (!ChannelAlpha && !KeyAlpha) { for (int y=0; yY(); y++) { System32BitPixel *s = (System32BitPixel*) (*pDC)[y]; for (int x=0; xX(); x++) { if (s[x].a < 0xff) { ChannelAlpha = true; y = pDC->Y(); break; } } } } } bool ExtraAlphaChannel = ChannelAlpha || (pDC->GetBits() > 8 ? KeyAlpha : 0); int ColourType; if (pDC->GetBits() <= 8) { if (pDC->Palette()) ColourType = PNG_COLOR_TYPE_PALETTE; else ColourType = PNG_COLOR_TYPE_GRAY; } else if (ExtraAlphaChannel) { ColourType = PNG_COLOR_TYPE_RGB_ALPHA; } else { ColourType = PNG_COLOR_TYPE_RGB; } LIBPNG png_set_IHDR(png_ptr, info_ptr, pDC->X(), pDC->Y(), 8, ColourType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); if (ColProfile.Type == GV_BINARY) { LIBPNG png_set_iCCP(png_ptr, info_ptr, (png_charp)"ColourProfile", 0, (png_const_bytep) ColProfile.Value.Binary.Data, (png_uint_32) ColProfile.Value.Binary.Length); } int TempLine = pDC->X() * ((pDC->GetBits() <= 8 ? 1 : 3) + (ExtraAlphaChannel ? 1 : 0)); uchar *TempBits = new uchar[pDC->Y() * TempLine]; if (Meter) Meter->SetRange(pDC->Y()); switch (pDC->GetBits()) { case 8: { // Output the palette LPalette *Pal = pDC->Palette(); if (Pal) { int Colours = Pal->GetSize(); LAutoPtr PngPal(new png_color[Colours]); if (PngPal) { for (int i=0; ir; PngPal[i].green = Rgb->g; PngPal[i].blue = Rgb->b; } } LIBPNG png_set_PLTE(png_ptr, info_ptr, PngPal, Colours); } } // Copy the pixels for (int y=0; yY(); y++) { uchar *s = (*pDC)[y]; Png8 *d = (Png8*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { *d++ = *s++; } } // Setup the transparent palette entry if (KeyAlpha) { static png_byte Trans[256]; for (uint n=0; nbit_depth = 8; //info_ptr->channels = 3 + (ExtraAlphaChannel ? 1 : 0); //info_ptr->color_type = PNG_COLOR_TYPE_RGB | (KeyAlpha ? PNG_COLOR_MASK_ALPHA : 0); for (int y=0; yY(); y++) { uint16 *s = (uint16*) (*pDC)[y]; if (pDC->GetBits() == 15) { if (KeyAlpha) { Png32 *d = (Png32*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = Rc15(*s); d->g = Gc15(*s); d->b = Bc15(*s); d->a = (d->r == Ar && d->g == Ag && d->b == Ab) ? 0 : 0xff; s++; d++; } } else { Png24 *d = (Png24*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = Rc15(*s); d->g = Gc15(*s); d->b = Bc15(*s); s++; d++; } } } else { if (KeyAlpha) { Png32 *d = (Png32*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = Rc16(*s); d->g = Gc16(*s); d->b = Bc16(*s); d->a = (d->r == Ar && d->g == Ag && d->b == Ab) ? 0 : 0xff; s++; d++; } } else { Png24 *d = (Png24*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = Rc16(*s); d->g = Gc16(*s); d->b = Bc16(*s); s++; d++; } } } } break; } case 24: { //info_ptr->bit_depth = 8; //info_ptr->channels = 3 + (KeyAlpha ? 1 : 0); //info_ptr->color_type = PNG_COLOR_TYPE_RGB | (KeyAlpha ? PNG_COLOR_MASK_ALPHA : 0); for (int y=0; yY(); y++) { System24BitPixel *s = (System24BitPixel*) (*pDC)[y]; if (KeyAlpha) { Png32 *d = (Png32*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = s->r; d->g = s->g; d->b = s->b; d->a = (s->r == Ar && s->g == Ag && s->b == Ab) ? 0 : 0xff; s++; d++; } } else { Png24 *d = (Png24*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = s->r; d->g = s->g; d->b = s->b; s++; d++; } } } break; } case 32: { //info_ptr->bit_depth = 8; //info_ptr->channels = 3 + (ExtraAlphaChannel ? 1 : 0); //info_ptr->color_type = PNG_COLOR_TYPE_RGB | (ExtraAlphaChannel ? PNG_COLOR_MASK_ALPHA : 0); for (int y=0; yY(); y++) { System32BitPixel *s = (System32BitPixel*) (*pDC)[y]; if (ChannelAlpha) { Png32 *d = (Png32*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = s->r; d->g = s->g; d->b = s->b; d->a = s->a; s++; d++; } } else if (KeyAlpha) { Png32 *d = (Png32*) (TempBits + (TempLine * y)); Png32 *e = d + pDC->X(); while (d < e) { if (s->a == 0 || (s->r == Ar && s->g == Ag && s->b == Ab) ) { d->r = 0; d->g = 0; d->b = 0; d->a = 0; } else { d->r = s->r; d->g = s->g; d->b = s->b; d->a = s->a; } s++; d++; } } else { Png24 *d = (Png24*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = s->r; d->g = s->g; d->b = s->b; s++; d++; } } } break; } default: { goto CleanUp; } } LArray row; if (row.Length(pDC->Y())) { for (int y=0; yY(); y++) { row[y] = TempBits + (TempLine * y); } LIBPNG png_set_rows(png_ptr, info_ptr, row.AddressOf()); LIBPNG png_write_png(png_ptr, info_ptr, 0, 0); Status = IoSuccess; } DeleteArray(TempBits); DeleteObj(pTemp); LIBPNG png_destroy_info_struct(png_ptr, &info_ptr); } CleanUp: LIBPNG png_destroy_write_struct(&png_ptr, NULL); } } return Status; } diff --git a/src/win/Lgi/Window.cpp b/src/win/Lgi/Window.cpp --- a/src/win/Lgi/Window.cpp +++ b/src/win/Lgi/Window.cpp @@ -1,1391 +1,1391 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Edit.h" #include "lgi/common/Popup.h" #include "lgi/common/ToolBar.h" #include "lgi/common/Panel.h" #include "lgi/common/Variant.h" #include "lgi/common/Token.h" #include "lgi/common/Button.h" #include "lgi/common/Notifications.h" #include "lgi/common/CssTools.h" #include "lgi/common/Menu.h" #define DEBUG_WINDOW_PLACEMENT 0 #define DEBUG_HANDLE_VIEW_KEY 0 #define DEBUG_HANDLE_VIEW_MOUSE 0 -#define DEBUG_SERIALIZE_STATE 1 +#define DEBUG_SERIALIZE_STATE 0 #define DEBUG_SETFOCUS 0 extern bool In_SetWindowPos; typedef UINT (WINAPI *ProcGetDpiForWindow)(_In_ HWND hwnd); typedef UINT (WINAPI *ProcGetDpiForSystem)(VOID); LLibrary User32("User32"); LPoint LGetDpiForWindow(HWND hwnd) { static bool init = false; static ProcGetDpiForWindow pGetDpiForWindow = NULL; static ProcGetDpiForSystem pGetDpiForSystem = NULL; if (!init) { init = true; pGetDpiForWindow = (ProcGetDpiForWindow) User32.GetAddress("GetDpiForWindow"); pGetDpiForSystem = (ProcGetDpiForSystem) User32.GetAddress("GetDpiForSystem"); } if (pGetDpiForWindow && pGetDpiForSystem) { auto Dpi = hwnd ? pGetDpiForWindow(hwnd) : pGetDpiForSystem(); return LPoint(Dpi, Dpi); } return LScreenDpi(); } /////////////////////////////////////////////////////////////////////////////////////////////// class HookInfo { public: int Flags; LView *Target; }; class LWindowPrivate { public: LArray Hooks; bool SnapToEdge; bool AlwaysOnTop; LWindowZoom Show; bool InCreate; LAutoPtr Wp; LPoint Dpi; // Focus stuff LViewI *Focus; LWindowPrivate() { Focus = NULL; InCreate = true; Show = LZoomNormal; SnapToEdge = false; AlwaysOnTop = false; } ~LWindowPrivate() { } ssize_t GetHookIndex(LView *Target, bool Create = false) { for (int i=0; iTarget = Target; n->Flags = 0; return (ssize_t)Hooks.Length() - 1; } } return -1; } }; /////////////////////////////////////////////////////////////////////////////////////////////// LWindow::LWindow() : LView(0) { _Window = this; d = new LWindowPrivate; SetStyle(GetStyle() | WS_TILEDWINDOW | WS_CLIPCHILDREN); SetStyle(GetStyle() & ~WS_CHILD); SetExStyle(GetExStyle() | WS_EX_CONTROLPARENT); LWindowsClass *c = LWindowsClass::Create(GetClass()); if (c) c->Register(); Visible(false); _Default = 0; _Lock = new LMutex("LWindow"); _QuitOnClose = false; } LWindow::~LWindow() { if (LAppInst && LAppInst->AppWnd == this) LAppInst->AppWnd = NULL; if (Menu) { Menu->Detach(); DeleteObj(Menu); } DeleteObj(_Lock); DeleteObj(d); } int LWindow::WaitThread() { // No thread to wait on... return 0; } bool LWindow::SetIcon(const char *Icon) { return CreateClassW32(LAppInst->Name(), LoadIcon(LProcessInst(), (LPCWSTR)Icon)) != 0; } LViewI *LWindow::GetFocus() { return d->Focus; } static LAutoString DescribeView(LViewI *v) { if (!v) return LAutoString(NewStr("NULL")); char s[512]; int ch = 0; ::LArray p; for (LViewI *i = v; i; i = i->GetParent()) { p.Add(i); } for (auto n=MIN(3, (ssize_t)p.Length()-1); n>=0; n--) { v = p[n]; ch += sprintf_s(s + ch, sizeof(s) - ch, ">%s", v->GetClass()); } return LAutoString(NewStr(s)); } static bool HasParentPopup(LViewI *v) { for (; v; v = v->GetParent()) { if (dynamic_cast(v)) return true; } return false; } void LWindow::SetFocus(LViewI *ctrl, FocusType type) { const char *TypeName = NULL; switch (type) { case GainFocus: TypeName = "Gain"; break; case LoseFocus: TypeName = "Lose"; break; case ViewDelete: TypeName = "Delete"; break; } switch (type) { case GainFocus: { LViewI *This = this; if (ctrl == This && d->Focus) { // The main LWindow is getting focus. // Check if we can re-focus the previous child focus... LView *v = d->Focus->GetGView(); if (v && !HasParentPopup(v)) { // We should never return focus to a popup, or it's child. if (!(v->WndFlags & GWF_FOCUS)) { // Yes, the child view doesn't think it has focus... // So re-focus it... if (v->Handle()) { // Non-virtual window... ::SetFocus(v->Handle()); } v->WndFlags |= GWF_FOCUS; v->OnFocus(true); v->Invalidate(); #if DEBUG_SETFOCUS LAutoString _set = DescribeView(ctrl); LAutoString _foc = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) refocusing: %s\n", _set.Get(), TypeName, _foc.Get()); #endif return; } } } // Check if the control already has focus if (d->Focus == ctrl) return; if (d->Focus) { LView *v = d->Focus->GetGView(); if (v) v->WndFlags &= ~GWF_FOCUS; d->Focus->OnFocus(false); d->Focus->Invalidate(); #if DEBUG_SETFOCUS LAutoString _foc = DescribeView(d->Focus); LgiTrace(".....defocus: %s\n", _foc.Get()); #endif } d->Focus = ctrl; if (d->Focus) { LView *v = d->Focus->GetGView(); if (v) v->WndFlags |= GWF_FOCUS; d->Focus->OnFocus(true); d->Focus->Invalidate(); #if DEBUG_SETFOCUS LAutoString _set = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) focusing\n", _set.Get(), TypeName); #endif } break; } case LoseFocus: { if (ctrl == d->Focus) { LView *v = d->Focus->GetGView(); if (v) { if (v->WndFlags & GWF_FOCUS) { // View thinks it has focus v->WndFlags &= ~GWF_FOCUS; d->Focus->OnFocus(false); // keep d->Focus pointer, as we want to be able to re-focus the child // view when we get focus again #if DEBUG_SETFOCUS LAutoString _ctrl = DescribeView(ctrl); LAutoString _foc = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) keep_focus: %s\n", _ctrl.Get(), TypeName, _foc.Get()); #endif } // else view doesn't think it has focus anyway... } else { // Non LView handler d->Focus->OnFocus(false); d->Focus->Invalidate(); d->Focus = NULL; } } else { /* LgiTrace("LWindow::SetFocus(%p.%s, %s) error on losefocus: %p(%s)\n", ctrl, ctrl ? ctrl->GetClass() : NULL, TypeName, d->Focus, d->Focus ? d->Focus->GetClass() : NULL); */ } break; } case ViewDelete: { if (ctrl == d->Focus) { #if DEBUG_SETFOCUS LgiTrace("LWindow::SetFocus(%p.%s, %s) delete_focus: %p(%s)\n", ctrl, ctrl ? ctrl->GetClass() : NULL, TypeName, d->Focus, d->Focus ? d->Focus->GetClass() : NULL); #endif d->Focus = NULL; } break; } } } bool LWindow::GetSnapToEdge() { return d->SnapToEdge; } void LWindow::SetSnapToEdge(bool b) { d->SnapToEdge = b; } bool LWindow::GetAlwaysOnTop() { return d->AlwaysOnTop; } void LWindow::SetAlwaysOnTop(bool b) { d->AlwaysOnTop = b; if (_View) SetWindowPos(_View, b ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); } void LWindow::Raise() { if (_View) { DWORD dwFGProcessId; DWORD dwFGThreadId = GetWindowThreadProcessId(_View, &dwFGProcessId); DWORD dwThisThreadId = GetCurrentThreadId(); AttachThreadInput(dwThisThreadId, dwFGThreadId, true); SetForegroundWindow(_View); BringWindowToTop(_View); if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus(%p)\n", __FILE__, __LINE__, _View); } ::SetFocus(_View); AttachThreadInput(dwThisThreadId, dwFGThreadId, false); } } LWindowZoom LWindow::GetZoom() { if (IsZoomed(Handle())) { return LZoomMax; } else if (IsIconic(Handle())) { return LZoomMin; } return LZoomNormal; } void LWindow::SetZoom(LWindowZoom i) { if (_View && IsWindowVisible(_View)) { switch (i) { case LZoomMax: { ShowWindow(Handle(), SW_MAXIMIZE); break; } case LZoomMin: { ShowWindow(Handle(), SW_MINIMIZE); break; } case LZoomNormal: { if (!Visible()) { Visible(true); } if (IsIconic(Handle()) || IsZoomed(Handle())) { ShowWindow(Handle(), SW_NORMAL); } LYield(); RECT r; GetWindowRect(Handle(), &r); if (r.left != Pos.x1 || r.top != Pos.y1) { SetWindowPos(Handle(), 0, Pos.x1, Pos.y1, Pos.X(), Pos.Y(), SWP_NOZORDER); } break; } } } d->Show = i; } bool LWindow::OnRequestClose(bool OsShuttingDown) { if (GetQuitOnClose()) { LCloseApp(); } return true; } bool LWindow::HandleViewMouse(LView *v, LMouse &m) { #if DEBUG_HANDLE_VIEW_MOUSE m.Trace("HandleViewMouse"); #endif for (int i=0; iHooks.Length(); i++) { if (d->Hooks[i].Flags & LMouseEvents) { LView *t = d->Hooks[i].Target; if (!t->OnViewMouse(v, m)) { #if DEBUG_HANDLE_VIEW_MOUSE if (m.IsMove()) LgiTrace(" Hook %i of %i ate mouse event: '%s'\n", i, d->Hooks.Length(), d->Hooks[i].Target->GetClass()); #endif return false; } } } #if DEBUG_HANDLE_VIEW_MOUSE if (!m.IsMove()) LgiTrace(" Passing mouse event to '%s'\n", v->GetClass()); #endif return true; } bool LWindow::HandleViewKey(LView *v, LKey &k) { #if DEBUG_HANDLE_VIEW_KEY char msg[256]; sprintf_s(msg, sizeof(msg), "HandleViewKey, v=%s", v ? v->GetClass() : "NULL"); k.Trace(msg); #endif // Any window in a pop up always gets the key... LViewI *p; for (p = v->GetParent(); p; p = p->GetParent()) { if (dynamic_cast(p)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Popup %s handling key.\n", p->GetClass()); #endif return v->OnKey(k); } } // Allow any hooks to see the key... #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" d->Hooks.Length()=%i.\n", (int)d->Hooks.Length()); #endif for (int i=0; iHooks.Length(); i++) { if (d->Hooks[i].Flags & LKeyEvents) { if (d->Hooks[i].Target->OnViewKey(v, k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Hook[%i] %s handling key.\n", i, d->Hooks[i].Target->GetClass()); #endif return true; } } } // Give the key to the focused window... if (d->Focus && d->Focus->OnKey(k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" d->Focus %s handling key.\n", d->Focus->GetClass()); #endif return true; } // Check default controls p = 0; if (k.c16 == VK_RETURN) { if (!_Default) p = _Default = FindControl(IDOK); else p = _Default; #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Using _Default ctrl (%s).\n", p ? p->GetClass() : "NULL"); #endif } else if (k.c16 == VK_ESCAPE) { p = FindControl(IDCANCEL); if (p) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Using IDCANCEL ctrl (%s).\n", p->GetClass()); #endif } } if (p && p->OnKey(k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Default control %s handled key.\n", p->GetClass()); #endif return true; } // Menu shortcut? if (Menu && Menu->OnKey(v, k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Menu handled key.\n"); #endif return true; } // Control shortcut? if (k.Down() && k.Alt() && k.c16 > ' ') { ShortcutMap Map; BuildShortcuts(Map); LViewI *c = Map.Find(ToUpper(k.c16)); if (c) { c->OnNotify(c, LNotifyActivate); return true; } } #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" No one handled key.\n"); #endif return false; } void LWindow::OnPaint(LSurface *pDC) { auto c = GetClient(); LCssTools Tools(this); Tools.PaintContent(pDC, c); } bool LWindow::Obscured() { RECT tRect; bool isObscured = false; if (GetWindowRect(_View, &tRect)) { RECT nRect; HWND walker = _View; while (walker = ::GetNextWindow(walker, GW_HWNDPREV)) { if (IsWindowVisible(walker)) { if ((::GetWindowRect(walker, &nRect))) { RECT iRect; IntersectRect(&iRect, &tRect, &nRect); if (iRect.bottom || iRect.top || iRect.left || iRect.right) { isObscured = true; break; } } } } } return isObscured; } bool LWindow::Visible() { return LView::Visible(); } void LWindow::Visible(bool v) { if (v) PourAll(); if (v) { SetStyle(GetStyle() | WS_VISIBLE); if (_View) { LWindowZoom z = d->Show; char *Cmd = 0; LAutoPtr Wp(new WINDOWPLACEMENT); if (Wp) { ZeroObj(*Wp.Get()); Wp->length = sizeof(*Wp); Wp->flags = 2; Wp->ptMaxPosition.x = -1; Wp->ptMaxPosition.y = -1; if (d->Show == LZoomMax) { Wp->showCmd = SW_MAXIMIZE; Cmd = "SW_MAXIMIZE"; } else if (d->Show == LZoomMin) { Wp->showCmd = SW_MINIMIZE; Cmd = "SW_MINIMIZE"; } else { Wp->showCmd = SW_NORMAL; Cmd = "SW_NORMAL"; } Wp->rcNormalPosition = Pos; #if DEBUG_WINDOW_PLACEMENT LgiTrace("%s:%i - SetWindowPlacement, pos=%s, show=%i\n", __FILE__, __LINE__, Pos.GetStr(), Wp->showCmd); #endif SetWindowPlacement(_View, Wp); if (d->InCreate) d->Wp = Wp; } } } else { #if DEBUG_WINDOW_PLACEMENT LgiTrace("%s:%i - Visible(%i)\n", __FILE__, __LINE__, v); #endif LView::Visible(v); } if (v) { OnZoom(d->Show); } } static bool IsAppWnd(HWND h) { if (!IsWindowVisible(h)) return false; auto flags = GetWindowLong(h, GWL_STYLE); if (flags & WS_POPUP) return false; return true; } bool LWindow::IsActive() { auto top = GetTopWindow(GetDesktopWindow()); while (top && !IsAppWnd(top)) top = ::GetWindow(top, GW_HWNDNEXT); return top == _View; } bool LWindow::SetActive() { if (!_View) return false; return SetWindowPos(_View, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE) != 0; } void LWindow::PourAll() { LRegion Client(GetClient()); LRegion Update; bool HasTools = false; { LRegion Tools; for (auto v: Children) { LView *k = dynamic_cast(v); if (k && k->_IsToolBar) { LRect OldPos = v->GetPos(); Update.Union(&OldPos); if (HasTools) { // 2nd and later toolbars if (v->Pour(Tools)) { if (!v->Visible()) { v->Visible(true); } auto vpos = v->GetPos(); if (OldPos != vpos) { // position has changed update... v->Invalidate(); } // Has it increased the size of the toolbar area? auto b = Tools.Bound(); if (vpos.y2 >= b.y2) { LRect Bar = Client; Bar.y2 = vpos.y2; Client.Subtract(&Bar); // LgiTrace("IncreaseToolbar=%s\n", Bar.GetStr()); } Tools.Subtract(&vpos); Update.Subtract(&vpos); // LgiTrace("vpos=%s\n", vpos.GetStr()); } } else { // First toolbar if (v->Pour(Client)) { HasTools = true; if (!v->Visible()) { v->Visible(true); } if (OldPos != v->GetPos()) { v->Invalidate(); } LRect Bar(v->GetPos()); Bar.x2 = GetClient().x2; Tools = Bar; Tools.Subtract(&v->GetPos()); Client.Subtract(&Bar); Update.Subtract(&Bar); } } } } } // LgiTrace("Client=%s\n", Client.Bound().GetStr()); for (auto v: Children) { LView *k = dynamic_cast(v); if (!(k && k->_IsToolBar)) { LRect OldPos = v->GetPos(); Update.Union(&OldPos); if (v->Pour(Client)) { if (!v->Visible()) { v->Visible(true); } if (OldPos != v->GetPos()) { // position has changed update... v->Invalidate(); } Client.Subtract(&v->GetPos()); Update.Subtract(&v->GetPos()); } else { // make the view not visible // v->Visible(FALSE); } } } for (int i=0; iMsg()) { case WM_DPICHANGED: { d->Dpi.x = HIWORD(Msg->A()); d->Dpi.y = LOWORD(Msg->A()); OnPosChange(); break; } case M_ASSERT_UI: { LAutoPtr Str((LString*)Msg->A()); extern void LAssertDlg(LString Msg, std::function Callback); if (Str) LAssertDlg(Str ? *Str : "Error: no msg.", NULL); break; } case M_SET_WINDOW_PLACEMENT: { /* Apparently if you use SetWindowPlacement inside the WM_CREATE handler, then the restored rect doesn't "stick", it gets stomped on by windows. So this code... RESETS it to be what we set earlier. Windows sucks. */ if (!d->Wp || !_View) break; LRect r = d->Wp->rcNormalPosition; if (!LView::Visible()) d->Wp->showCmd = SW_HIDE; #if DEBUG_WINDOW_PLACEMENT LgiTrace("%s:%i - SetWindowPlacement, pos=%s, show=%i\n", __FILE__, __LINE__, r.GetStr(), d->Wp->showCmd); #endif SetWindowPlacement(_View, d->Wp); d->Wp.Reset(); break; } case WM_SYSCOLORCHANGE: { LColour::OnChange(); break; } case WM_WINDOWPOSCHANGING: { bool Icon = IsIconic(Handle()) != 0; bool Zoom = IsZoomed(Handle()) != 0; if (!Icon && (_Dialog || !Zoom)) { WINDOWPOS *Info = (LPWINDOWPOS) Msg->b; if (!Info) break; if (Info->flags == (SWP_NOSIZE | SWP_NOMOVE) && _Dialog) { // Info->flags |= SWP_NOZORDER; Info->hwndInsertAfter = _Dialog->Handle(); } if (GetMinimumSize().x && GetMinimumSize().x > Info->cx) { Info->cx = GetMinimumSize().x; } if (GetMinimumSize().y && GetMinimumSize().y > Info->cy) { Info->cy = GetMinimumSize().y; } /* This is broken on windows 10... windows get stuck on the edge of the desktop. RECT Rc; if (d->SnapToEdge && SystemParametersInfo(SPI_GETWORKAREA, 0, &Rc, SPIF_SENDCHANGE)) { LRect r = Rc; LRect p(Info->x, Info->y, Info->x + Info->cx - 1, Info->y + Info->cy - 1); if (r.Valid() && p.Valid()) { int Snap = 12; if (abs(p.x1 - r.x1) <= Snap) { // Snap left edge Info->x = r.x1; } else if (abs(p.x2 - r.x2) <= Snap) { // Snap right edge Info->x = r.x2 - Info->cx + 1; } if (abs(p.y1 - r.y1) <= Snap) { // Snap top edge Info->y = r.y1; } else if (abs(p.y2 - r.y2) <= Snap) { // Snap bottom edge Info->y = r.y2 - Info->cy + 1; } } } */ } break; } case WM_SIZE: { if (Visible()) { LWindowZoom z = d->Show; switch (Msg->a) { case SIZE_MINIMIZED: { z = LZoomMin; break; } case SIZE_MAXIMIZED: { z = LZoomMax; break; } case SIZE_RESTORED: { z = LZoomNormal; break; } } if (z != d->Show) { OnZoom(d->Show = z); } } Status = LView::OnEvent(Msg) != 0; break; } case WM_CREATE: { if (d->AlwaysOnTop) SetAlwaysOnTop(true); PourAll(); OnCreate(); if (!_Default) { _Default = FindControl(IDOK); if (_Default) _Default->Invalidate(); } d->InCreate = false; if (d->Wp) { PostEvent(M_SET_WINDOW_PLACEMENT); } break; } case WM_WINDOWPOSCHANGED: { d->Wp.Reset(); Status = LView::OnEvent(Msg) != 0; break; } case WM_QUERYENDSESSION: case WM_CLOSE: { bool QuitApp; bool OsShuttingDown = Msg->Msg() == WM_QUERYENDSESSION; if (QuitApp = OnRequestClose(OsShuttingDown)) { Quit(); } if (Msg->Msg() == WM_CLOSE) { return 0; } else { return QuitApp; } break; } case WM_SYSCOMMAND: { if (Msg->a == SC_CLOSE) { if (OnRequestClose(false)) { Quit(); } return 0; } else { Status = LView::OnEvent(Msg) != 0; } break; } case WM_DROPFILES: { HDROP hDrop = (HDROP) Msg->a; if (hDrop) { LArray FileNames; int Count = 0; Count = DragQueryFileW(hDrop, -1, NULL, 0); for (int i=0; i 0) { FileNames.Add(WideToUtf8(FileName)); } } OnReceiveFiles(FileNames); FileNames.DeleteArrays(); } break; } case M_HANDLEMOUSEMOVE: { // This receives events fired from the LMouseHookPrivate class so that // non-LGI windows create mouse hook events as well. LTempView v((OsView)Msg->B()); LMouse m; m.x = LOWORD(Msg->A()); m.y = HIWORD(Msg->A()); HandleViewMouse(&v, m); break; } case M_COMMAND: { HWND OurWnd = Handle(); // copy onto the stack, because // we might lose the 'this' object in the // OnCommand handler which would delete // the memory containing the handle. Status = OnCommand((int) Msg->a, 0, (OsView) Msg->b); if (!IsWindow(OurWnd)) { // The window was deleted so break out now break; } // otherwise fall thru to the LView handler } default: { Status = (int) LView::OnEvent(Msg); break; } } return Status; } LPoint LWindow::GetDpi() { if (!d->Dpi.x) d->Dpi = LGetDpiForWindow(_View); return d->Dpi; } LPointF LWindow::GetDpiScale() { auto Dpi = GetDpi(); LPointF r( Dpi.x / 96.0, Dpi.y / 96.0 ); return r; } LRect &LWindow::GetPos() { if (_View && IsZoomed(_View)) { static LRect r; RECT rc; GetWindowRect(_View, &rc); r = rc; return r; } return Pos; } void LWindow::OnPosChange() { PourAll(); } bool LWindow::RegisterHook(LView *Target, LWindowHookType EventType, int Priority) { bool Status = false; if (Target && EventType) { auto i = d->GetHookIndex(Target, true); if (i >= 0) { d->Hooks[i].Flags = EventType; Status = true; } } return Status; } LViewI *LWindow::GetDefault() { return _Default; } void LWindow::SetDefault(LViewI *v) { #if WINNATIVE LButton *Btn; if (Btn = dynamic_cast(_Default)) Btn->Default(false); #endif _Default = v; #if WINNATIVE if (Btn = dynamic_cast(_Default)) Btn->Default(true); #endif } bool LWindow::UnregisterHook(LView *Target) { auto i = d->GetHookIndex(Target); if (i >= 0) { d->Hooks.DeleteAt(i); return true; } return false; } bool LWindow::SerializeState(LDom *Store, const char *FieldName, bool Load) { if (!Store || !FieldName) return false; #if DEBUG_SERIALIZE_STATE LgiTrace("LWindow::SerializeState(%p, %s, %i)\n", Store, FieldName, Load); #endif if (Load) { LVariant v; if (Store->GetValue(FieldName, v) && v.Str()) { LRect Position(0, 0, -1, -1); LWindowZoom State = LZoomNormal; #if DEBUG_SERIALIZE_STATE LgiTrace("\t::SerializeState:%i v=%s\n", __LINE__, v.Str()); #endif LToken t(v.Str(), ";"); for (int i=0; iSetValue(FieldName, v)) return false; } return true; } void LWindow::OnTrayClick(LMouse &m) { if (m.Down() || m.IsContextMenu()) { LSubMenu RClick; OnTrayMenu(RClick); if (GetMouse(m, true)) { #if WINNATIVE SetForegroundWindow(Handle()); #endif int Result = RClick.Float(this, m); #if WINNATIVE PostMessage(Handle(), WM_NULL, 0, 0); #endif OnTrayMenuResult(Result); } } }