ide/PictureEdit: Avoid crashes on zooming on an image that failed loading
[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                   bitmap.Convert(null, pixelFormat888, null);
139
140                //if(!eWindow_GetStartWidth(window) || !eWindow_GetStartHeight(window))
141                {
142                   Size size = initSize;  // what's the use of retrieving initSize
143                   size.w = bitmap.width;
144                   size.h = bitmap.height;
145                   clientSize = size;
146
147                   /*
148                   Move(eWindow_GetStartX(window), eWindow_GetStartY(window), 
149                      (!eWindow_GetStartWidth(window)) ? (A_CLIENT|bitmap.width) : eWindow_GetStartWidth(window), 
150                      (!eWindow_GetStartHeight(window)) ? (A_CLIENT|bitmap.height) : eWindow_GetStartHeight(window));
151                   */
152
153                   /*
154                   Move(eWindow_GetStartX(window), eWindow_GetStartY(window), 
155                      (!) ? (A_CLIENT|bitmap.width) : eWindow_GetStartWidth(window), 
156                      (!eWindow_GetStartHeight(window)) ? (A_CLIENT|bitmap.height) : eWindow_GetStartHeight(window));
157                   */
158                }
159                scrollArea = Size {bitmap.width, bitmap.height };
160             }
161             else
162                delete bitmap;
163          }
164
165          if(bitmap)
166          {
167             switch(bitmap.pixelFormat)
168             {
169                case pixelFormat8:
170                   imageModeIndexedItem.checked = true;
171                   break;
172                case pixelFormat888:
173                   imageModeRGBItem.checked = true;
174                   imageModeColorTableItem.disabled = true;
175                   break;
176             }
177          }
178       }
179    }
180
181    void OnRedraw(Surface surface)
182    {
183       if(bitmap)
184       {
185          int w = (int)(bitmap.width * zoomFactor);
186          int h = (int)(bitmap.height * zoomFactor);
187          if(w == bitmap.width && h == bitmap.height)
188          {
189             surface.Blit(bitmap, 
190                Max(0, (clientSize.w - w) / 2), Max(0, (clientSize.h - h) / 2), 
191                scroll.x, scroll.y, w, h);
192          }
193          else
194          {
195             surface.Filter(bitmap, 
196                Max(0, (clientSize.w - w) / 2), Max(0, (clientSize.h - h) / 2), 
197                (int)(scroll.x / zoomFactor), (int)(scroll.y / zoomFactor), w, h, 
198                bitmap.width, bitmap.height);
199          }
200       }
201    }
202
203    void OnScroll(ScrollBarAction action, int position, Key key)
204    {
205       Update(null);
206    }
207
208    bool OnKeyHit(Key key, unichar ch)
209    {
210       switch(key)
211       {
212          case equal:
213          case keyPadPlus:
214             if(bitmap && zoomFactor < 25)
215             {
216                float x = 0.5f, y = 0.5f;
217                if(bitmap.width * zoomFactor > clientSize.w) 
218                   x = scroll.x / (bitmap.width * zoomFactor - clientSize.w);
219                if(bitmap.height * zoomFactor > clientSize.h) 
220                   y = scroll.y / (bitmap.height * zoomFactor - clientSize.h);
221
222                zoomFactor *= 1.5;
223                scrollArea = Size { bitmap.width * zoomFactor,  bitmap.height * zoomFactor };
224
225                scroll = Point { 
226                      (int)(Max(0, bitmap.width * zoomFactor - clientSize.w) * x), 
227                      (int)(Max(0, bitmap.height * zoomFactor - clientSize.h) * y) };
228
229                Update(null);
230             }
231             break;
232          case minus:
233          case keyPadMinus:
234             if(bitmap && zoomFactor > 0.05)
235             {
236                float x = 0.5f, y = 0.5f;
237                if(bitmap.width * zoomFactor > clientSize.w) 
238                   x = scroll.x / (bitmap.width * zoomFactor - clientSize.w);
239                if(bitmap.height * zoomFactor > clientSize.w) 
240                   y = scroll.y / (bitmap.height * zoomFactor - clientSize.h);
241
242                zoomFactor /= 1.5;
243                scrollArea = Size { bitmap.width * zoomFactor, bitmap.height * zoomFactor };
244
245                scroll = Point { 
246                      (int)(Max(0, bitmap.width * zoomFactor - clientSize.w) * x),
247                      (int)(Max(0, bitmap.height * zoomFactor - clientSize.h) * y) };
248
249                Update(null);
250             }
251             break;
252       }
253       return true;
254    }
255
256    bool OnSaveFile(char * fileName)
257    {
258       bool result = false;
259       if(bitmap)
260       {
261          if(bitmap.Save(fileName, 
262             ((FileType *)pictureEditFileDialog.types)[pictureEditFileDialog.fileType].typeExtension, (void *) bool::true))
263          {
264             modifiedDocument = false;
265             result = true;
266          }
267       }
268       return result;
269    }
270    
271    PictureEdit()
272    {
273       zoomFactor = 1.0f;
274       return true;
275    }
276
277 }
278
279 class PictureEditColorTable : Window
280 {
281    hasClose = true;
282    text = $"Color Table";
283    background = formColor;
284    minClientSize = Size { 400, 400 };
285
286    Button button
287    {
288       parent = this, hotKey = escape, size = { 80 }, text = $"Close";
289       anchor = Anchor { right = 10, bottom = 10 };
290       NotifyClicked = ButtonCloseDialog;
291    };
292
293    void OnRedraw(Surface surface)
294    {
295       PictureEdit picture = (PictureEdit)master;
296       Bitmap bitmap = picture.bitmap;
297       int c;
298       for(c = 0; c < 256; c++)
299       {
300          int x = (c % 16) * 16;
301          int y = (c / 16) * 16;
302          surface.SetBackground(bitmap.palette[c]);
303          surface.Area(10 + x, 30 + y, 10 + x + 15, 30 + y + 15);
304       }
305    }
306 }
307
308 class ColorBox : Window
309 {
310    size = { 32, 32 };
311    borderStyle = deepContour;
312 }
313
314 #ifdef _DEBUG
315 class AdjustHSV : Window
316 {
317    size = { 400, 300 };
318
319    background = formColor;
320    ColorHSV target;
321    ColorHSV replace;
322    replace = { 248, 100, 71 }; //Color { 26, 0, 183 };
323    target = { 207, 61, 71 };
324    hasClose = true;
325
326    Button button1
327    {
328       this, text = $"Go", position = { 296, 104 }, isDefault = true;
329
330       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
331       {
332          PictureEdit picture = (PictureEdit)master;
333          Bitmap bitmap = picture.bitmap;
334          double h = 1.0f, s = 0.80f, v = 1.24f;
335          double tolH = 1;
336          double tolS = 1;
337          double tolV = 1;
338          
339          h = target.h - replace.h;
340          s = target.s / replace.s;
341          v = target.v / replace.v;
342
343          for(y = 0; y<bitmap.height; y++)
344          {
345             for(x = 0; x<bitmap.width; x++)
346             {
347                ColorAlpha color = ((ColorAlpha *)bitmap.picture)[y * bitmap.stride + x];
348                ColorHSV hsv = color;
349                double diffH = Abs(hsv.h/360 - replace.h/360);
350                double diffS = Abs(hsv.s - replace.s)/100.0;
351                double diffV = Abs(hsv.v - replace.v)/100.0;
352                if(diffH <= tolH && diffS <= tolS && diffV <= tolV)
353                {
354                   hsv.h += h;
355                   hsv.s *= s;
356                   hsv.v *= v;
357                   color.color = hsv;
358                   ((ColorAlpha *)bitmap.picture)[y * bitmap.stride + x] = color;
359                }
360             }
361          }
362          picture.Update(null);
363          picture.modifiedDocument = true;
364          Destroy(0);
365          return true;
366       }
367    };
368
369    EditBox hBox { this, text = "H", size = { 78, 19 }, position = { 176, 80 } };
370    EditBox sBox { this, text = "S", size = { 78, 19 }, position = { 176, 120 } };
371    EditBox vBox { this, text = "V", size = { 78, 19 }, position = { 176, 160 } };
372    Label label1 { this, size = { 7, 13 }, position = { 152, 80 }, labeledWindow = hBox };
373    Label label2 { this, size = { 6, 13 }, position = { 152, 120 }, labeledWindow = sBox };
374    Label label3 { this, size = { 6, 13 }, position = { 152, 168 }, labeledWindow = vBox };
375    ColorBox original { this, position = { 10, 10 }, background = replace };
376    ColorBox result { this, position = { 10, 100 }, background = target };
377 }
378 #endif