ide/PictureEdit: Improved viewing alpha blended PNGs
[sdk] / ide / src / documents / PictureEdit.ec
1 /****************************************************************************
2    ECERE IDE
3
4    Copyright (c) 2003 Jerome Jacovella-St-Louis
5    All Rights Reserved.
6
7    pictureEdit.ec - Picture Editor Control
8 ****************************************************************************/
9 #ifdef ECERE_STATIC
10 import static "ecere"
11 #else
12 import "ecere"
13 #endif
14
15 #define ID_IMAGE_MODE_COLORTABLE 9
16 #define ID_IMAGE_MODE_INDEXED    10
17 #define ID_IMAGE_MODE_RGB        11
18
19 static Array<FileFilter> filters
20 { [
21    { $"Image Files (*.jpg, *.jpeg, *.bmp, *.pcx, *.png, *.gif)", "jpg, jpeg, bmp, pcx, png, gif" },
22    { $"All files", null }
23 ] };
24
25 static Array<FileType> types
26 { [
27    { $"Based on extension", null,  never },
28    { $"JPG Image",          "jpg", always },
29    { $"BMP Image",          "bmp", always },
30    { $"PCX Image",          "pcx", always },
31    { $"PNG Image",          "png", always },
32    { $"GIF Image",          "gif", always }
33 ] };
34
35 FileDialog pictureEditFileDialog { filters = filters.array, sizeFilters = filters.count * sizeof(FileFilter), types = types.array, sizeTypes = types.count * sizeof(FileType) };
36
37 class PictureEdit : Window
38 {
39    background = black;
40    isDocument = true;
41    isActiveClient = true;
42    menu = Menu { };
43
44    OnHScroll = OnScroll;
45    OnVScroll = OnScroll;
46
47    float zoomFactor;
48    char fileName[MAX_LOCATION];
49    Bitmap bitmap;
50
51    //saveDialog = pictureEditFileDialog;
52
53    Menu fileMenu { menu, $"File", f }
54       MenuItem { fileMenu, $"Save", s, ctrlS, NotifySelect = MenuFileSave };
55       MenuItem { fileMenu, $"Save As...", a, NotifySelect = MenuFileSaveAs };
56    Menu imageMenu { menu, $"Image", i };
57       Menu modeMenu { imageMenu, $"Mode", m };
58          MenuItem imageModeIndexedItem
59          {
60             modeMenu, $"Indexed Color...", i, isRadio = true;
61             bool NotifySelect(MenuItem selection, Modifiers mods)
62             {
63                if(bitmap)
64                {
65                   ColorAlpha * palette = bitmap.Quantize(0, 255);
66                   /*
67                   eBitmap_Convert(null, bitmap, PixelFormat8, palette);
68                   bitmap.allocatePalette = true;
69                   */
70
71                   imageModeColorTableItem.disabled = false;
72                   Update(null);
73                   modifiedDocument = true;
74                }
75                return true;
76             }
77          };
78          MenuItem imageModeRGBItem
79          {
80             modeMenu, $"RGB Color", r, isRadio = true;
81             bool NotifySelect(MenuItem selection, Modifiers mods)
82             {
83                if(bitmap)
84                   bitmap.Convert(null, pixelFormat888, null);
85                imageModeColorTableItem.disabled = true;
86                Update(null);
87                modifiedDocument = true;
88                return true;
89             }
90          };
91          MenuDivider { modeMenu };
92          MenuItem imageModeColorTableItem
93          {
94             modeMenu, $"Color Table", r;
95             bool NotifySelect(MenuItem selection, Modifiers mods)
96             {
97                if(bitmap)
98                {
99                   PictureEditColorTable colorTable { master = this };
100                   colorTable.Modal();
101                   Update(null);
102                }
103                return true;
104             }
105          };
106          #ifdef _DEBUG
107          MenuDivider { imageMenu };
108          MenuItem adjustHSVItem
109          {
110             imageMenu, $"Adjust Hue, Saturation, Value", h;
111             bool NotifySelect(MenuItem selection, Modifiers mods)
112             {
113                if(bitmap)
114                {
115                   AdjustHSV adjustHSV { master = this };
116                   adjustHSV.Modal();
117                   Update(null);
118                }
119                return true;
120             }
121          };
122          #endif
123
124    property char * bitmapFile
125    {
126       set
127       {
128          if(value)
129          {
130             strcpy(fileName, value);
131          }
132          if(fileName[0])
133          {
134             bitmap = Bitmap {};
135             if(bitmap.Load(fileName, null, null))
136             {
137                if(bitmap.pixelFormat == pixelFormatRGBA)
138                {
139                   bitmap.alphaBlend = true;
140                   bitmap.Convert(null, pixelFormat888, null);
141                }
142                //if(!eWindow_GetStartWidth(window) || !eWindow_GetStartHeight(window))
143                {
144                   Size size = initSize;  // what's the use of retrieving initSize
145                   size.w = bitmap.width;
146                   size.h = bitmap.height;
147                   clientSize = size;
148
149                   /*
150                   Move(eWindow_GetStartX(window), eWindow_GetStartY(window),
151                      (!eWindow_GetStartWidth(window)) ? (A_CLIENT|bitmap.width) : eWindow_GetStartWidth(window),
152                      (!eWindow_GetStartHeight(window)) ? (A_CLIENT|bitmap.height) : eWindow_GetStartHeight(window));
153                   */
154
155                   /*
156                   Move(eWindow_GetStartX(window), eWindow_GetStartY(window),
157                      (!) ? (A_CLIENT|bitmap.width) : eWindow_GetStartWidth(window),
158                      (!eWindow_GetStartHeight(window)) ? (A_CLIENT|bitmap.height) : eWindow_GetStartHeight(window));
159                   */
160                }
161                scrollArea = Size {bitmap.width, bitmap.height };
162             }
163             else
164                delete bitmap;
165          }
166
167          if(bitmap)
168          {
169             switch(bitmap.pixelFormat)
170             {
171                case pixelFormat8:
172                   imageModeIndexedItem.checked = true;
173                   break;
174                case pixelFormat888:
175                   imageModeRGBItem.checked = true;
176                   imageModeColorTableItem.disabled = true;
177                   break;
178             }
179          }
180       }
181    }
182
183    void OnRedraw(Surface surface)
184    {
185       if(bitmap)
186       {
187          int w = (int)(bitmap.width * zoomFactor);
188          int h = (int)(bitmap.height * zoomFactor);
189          if(w == bitmap.width && h == bitmap.height)
190          {
191             surface.Blit(bitmap,
192                Max(0, (clientSize.w - w) / 2), Max(0, (clientSize.h - h) / 2),
193                scroll.x, scroll.y, w, h);
194          }
195          else
196          {
197             surface.Filter(bitmap,
198                Max(0, (clientSize.w - w) / 2), Max(0, (clientSize.h - h) / 2),
199                (int)(scroll.x / zoomFactor), (int)(scroll.y / zoomFactor), w, h,
200                bitmap.width, bitmap.height);
201          }
202       }
203    }
204
205    void OnScroll(ScrollBarAction action, int position, Key key)
206    {
207       Update(null);
208    }
209
210    bool OnKeyHit(Key key, unichar ch)
211    {
212       switch(key)
213       {
214          case equal:
215          case keyPadPlus:
216             if(bitmap && zoomFactor < 25)
217             {
218                float x = 0.5f, y = 0.5f;
219                if(bitmap.width * zoomFactor > clientSize.w)
220                   x = scroll.x / (bitmap.width * zoomFactor - clientSize.w);
221                if(bitmap.height * zoomFactor > clientSize.h)
222                   y = scroll.y / (bitmap.height * zoomFactor - clientSize.h);
223
224                zoomFactor *= 1.5;
225                scrollArea = Size { bitmap.width * zoomFactor,  bitmap.height * zoomFactor };
226
227                scroll = Point {
228                      (int)(Max(0, bitmap.width * zoomFactor - clientSize.w) * x),
229                      (int)(Max(0, bitmap.height * zoomFactor - clientSize.h) * y) };
230
231                Update(null);
232             }
233             break;
234          case minus:
235          case keyPadMinus:
236             if(bitmap && zoomFactor > 0.05)
237             {
238                float x = 0.5f, y = 0.5f;
239                if(bitmap.width * zoomFactor > clientSize.w)
240                   x = scroll.x / (bitmap.width * zoomFactor - clientSize.w);
241                if(bitmap.height * zoomFactor > clientSize.w)
242                   y = scroll.y / (bitmap.height * zoomFactor - clientSize.h);
243
244                zoomFactor /= 1.5;
245                scrollArea = Size { bitmap.width * zoomFactor, bitmap.height * zoomFactor };
246
247                scroll = Point {
248                      (int)(Max(0, bitmap.width * zoomFactor - clientSize.w) * x),
249                      (int)(Max(0, bitmap.height * zoomFactor - clientSize.h) * y) };
250
251                Update(null);
252             }
253             break;
254       }
255       return true;
256    }
257
258    bool OnSaveFile(char * fileName)
259    {
260       bool result = false;
261       if(bitmap)
262       {
263          if(bitmap.Save(fileName,
264             ((FileType *)pictureEditFileDialog.types)[pictureEditFileDialog.fileType].typeExtension, (void *) bool::true))
265          {
266             modifiedDocument = false;
267             result = true;
268          }
269       }
270       return result;
271    }
272
273    PictureEdit()
274    {
275       zoomFactor = 1.0f;
276       return true;
277    }
278
279 }
280
281 class PictureEditColorTable : Window
282 {
283    hasClose = true;
284    text = $"Color Table";
285    background = formColor;
286    minClientSize = Size { 400, 400 };
287
288    Button button
289    {
290       parent = this, hotKey = escape, size = { 80 }, text = $"Close";
291       anchor = Anchor { right = 10, bottom = 10 };
292       NotifyClicked = ButtonCloseDialog;
293    };
294
295    void OnRedraw(Surface surface)
296    {
297       PictureEdit picture = (PictureEdit)master;
298       Bitmap bitmap = picture.bitmap;
299       int c;
300       for(c = 0; c < 256; c++)
301       {
302          int x = (c % 16) * 16;
303          int y = (c / 16) * 16;
304          surface.SetBackground(bitmap.palette[c]);
305          surface.Area(10 + x, 30 + y, 10 + x + 15, 30 + y + 15);
306       }
307    }
308 }
309
310 class ColorBox : Window
311 {
312    size = { 32, 32 };
313    borderStyle = deepContour;
314 }
315
316 #ifdef _DEBUG
317 class AdjustHSV : Window
318 {
319    size = { 400, 300 };
320
321    background = formColor;
322    ColorHSV target;
323    ColorHSV replace;
324    replace = { 248, 100, 71 }; //Color { 26, 0, 183 };
325    target = { 207, 61, 71 };
326    hasClose = true;
327
328    Button button1
329    {
330       this, text = $"Go", position = { 296, 104 }, isDefault = true;
331
332       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
333       {
334          PictureEdit picture = (PictureEdit)master;
335          Bitmap bitmap = picture.bitmap;
336          double h = 1.0f, s = 0.80f, v = 1.24f;
337          double tolH = 1;
338          double tolS = 1;
339          double tolV = 1;
340
341          h = target.h - replace.h;
342          s = target.s / replace.s;
343          v = target.v / replace.v;
344
345          for(y = 0; y<bitmap.height; y++)
346          {
347             for(x = 0; x<bitmap.width; x++)
348             {
349                ColorAlpha color = ((ColorAlpha *)bitmap.picture)[y * bitmap.stride + x];
350                ColorHSV hsv = color;
351                double diffH = Abs(hsv.h/360 - replace.h/360);
352                double diffS = Abs(hsv.s - replace.s)/100.0;
353                double diffV = Abs(hsv.v - replace.v)/100.0;
354                if(diffH <= tolH && diffS <= tolS && diffV <= tolV)
355                {
356                   hsv.h += h;
357                   hsv.s *= s;
358                   hsv.v *= v;
359                   color.color = hsv;
360                   ((ColorAlpha *)bitmap.picture)[y * bitmap.stride + x] = color;
361                }
362             }
363          }
364          picture.Update(null);
365          picture.modifiedDocument = true;
366          Destroy(0);
367          return true;
368       }
369    };
370
371    EditBox hBox { this, text = "H", size = { 78, 19 }, position = { 176, 80 } };
372    EditBox sBox { this, text = "S", size = { 78, 19 }, position = { 176, 120 } };
373    EditBox vBox { this, text = "V", size = { 78, 19 }, position = { 176, 160 } };
374    Label label1 { this, size = { 7, 13 }, position = { 152, 80 }, labeledWindow = hBox };
375    Label label2 { this, size = { 6, 13 }, position = { 152, 120 }, labeledWindow = sBox };
376    Label label3 { this, size = { 6, 13 }, position = { 152, 168 }, labeledWindow = vBox };
377    ColorBox original { this, position = { 10, 10 }, background = replace };
378    ColorBox result { this, position = { 10, 100 }, background = target };
379 }
380 #endif