1//
2//  PRCurvesView.m
3//  PRICE
4//
5//  Created by Riccardo Mottola on 7 August 2011.
6//  Copyright 2011-2014 Riccardo Mottola. All rights reserved.
7//
8// This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
9// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
10
11#import "PRCurvesView.h"
12#import "PRCCurves.h"
13
14
15@implementation PRCurvesView
16
17- (id)initWithFrame:(NSRect)frame
18  {
19    self = [super initWithFrame:frame];
20    if (self)
21      {
22	hasColor = NO;
23
24        pathLumi = [[PRCurvesPath alloc] init];
25        pathR    = [[PRCurvesPath alloc] init];
26        pathG    = [[PRCurvesPath alloc] init];
27        pathB    = [[PRCurvesPath alloc] init];
28
29	pathHLumi = [[NSBezierPath alloc] init];
30	pathHR = [[NSBezierPath alloc] init];
31	pathHG = [[NSBezierPath alloc] init];
32	pathHB = [[NSBezierPath alloc] init];
33
34	pathHLumiTr = [[NSBezierPath alloc] init];
35	pathHRTr = [[NSBezierPath alloc] init];
36	pathHGTr = [[NSBezierPath alloc] init];
37	pathHBTr = [[NSBezierPath alloc] init];
38
39	[self initPathLumi];
40	[self initPathR];
41	[self initPathG];
42	[self initPathB];
43
44	histogramScale = 0.5;
45      }
46    return self;
47}
48
49- (void)initPath:(PRCurvesPath *)p
50{
51  [p removeAllPoints];
52
53  [p moveToPoint: NSMakePoint(0, 0)];
54  [p curveToPoint: NSMakePoint(255, 255) controlPoint1: NSMakePoint(50, 0) controlPoint2: NSMakePoint(205, 255)];
55}
56
57- (void)initPathLumi
58{
59  [self initPath: pathLumi];
60}
61
62- (void)initPathR
63{
64  [self initPath: pathR];
65}
66
67- (void)initPathG
68{
69  [self initPath: pathG];
70}
71
72- (void)initPathB
73{
74  [self initPath: pathB];
75}
76
77
78- (void)dealloc
79{
80  [pathLumi release];
81  [pathR release];
82  [pathG release];
83  [pathB release];
84
85  [pathHLumi release];
86  [pathHR release];
87  [pathHG release];
88  [pathHB release];
89
90  [pathHLumiTr release];
91  [pathHRTr release];
92  [pathHGTr release];
93  [pathHBTr release];
94
95  [super dealloc];
96}
97
98-(BOOL)hasColor
99{
100  return hasColor;
101}
102
103-(void)setFilterController: (PRCCurves *)aFilterController;
104{
105  controller = aFilterController;
106}
107
108-(void)setBlackPoint: (int)value
109{
110  PRCurvesPath *path;
111  NSBezierPathElement pe;
112  NSPoint cp[3];
113  float diff;
114
115  path = pathLumi;
116  pe = [path elementAtIndex: 0 associatedPoints: cp];
117  diff = cp[0].x - (float)value;
118  if ((pe == NSMoveToBezierPathElement) || (pe == NSLineToBezierPathElement))
119    {
120      /* if we have a straight line, we move the next control point too to preserve the curve */
121      NSBezierPathElement pe2;
122      NSPoint cp2[3];
123
124      if  ([path elementCount] > 0)
125	{
126	  cp[0].x = (float)value;
127	  pe2 = [path elementAtIndex: 1 associatedPoints: cp2];
128	  if (pe2 == NSCurveToBezierPathElement)
129	    {
130	      NSLog(@"old cp2-0.x: %f", cp2[0].x);
131	      cp2[0].x = cp2[0].x - diff;
132	      NSLog(@"new cp2-0.x: %f", cp2[0].x);
133	      [path setAssociatedPoints: cp2 atIndex: 1];
134	    }
135	}
136      [path setAssociatedPoints: cp atIndex: 0];
137    }
138}
139
140-(void)setWhitePoint: (int)value
141{
142  PRCurvesPath *path;
143  NSBezierPathElement pe;
144  NSPoint cp[3];
145
146  path = pathLumi;
147  pe = [path elementAtIndex: [path elementCount]-1 associatedPoints: cp];
148  if (pe == NSMoveToBezierPathElement)
149    cp[0].x = value;
150  else if (pe == NSLineToBezierPathElement)
151    cp[0].x = value;
152  else if (pe == NSCurveToBezierPathElement)
153    {
154      float diff;
155
156      diff = cp[2].x - (float)value;
157      cp[2].x = value;
158      cp[1].x = cp[1].x - diff;
159      //cp[2].x = cp[2].x - diff;
160    }
161  [path setAssociatedPoints: cp atIndex: [path elementCount]-1];
162}
163
164
165-(PRCurvesPath *)luminancePath
166{
167  return pathLumi;
168}
169
170-(PRCurvesPath *)redPath
171{
172  return pathR;
173}
174
175-(PRCurvesPath *)greenPath
176{
177  return pathG;
178}
179
180-(PRCurvesPath *)bluePath
181{
182  return pathB;
183}
184
185- (void)drawRect:(NSRect)rect
186{
187  [[NSColor whiteColor] set];
188  [NSBezierPath fillRect: [self bounds]];
189
190  if (hasColor)
191    {
192      [[NSColor colorWithDeviceRed:1.0 green:0.0 blue:0.0 alpha:1.0] set];
193      [pathHR stroke];
194      [[NSColor colorWithDeviceRed:1.0 green:0.0 blue:0.0 alpha:0.7] set];
195      [pathHR fill];
196      [[NSColor colorWithDeviceRed:1.0 green:0.0 blue:0.0 alpha:1.0] set];
197      [pathHRTr stroke];
198      [[NSColor colorWithDeviceRed:1.0 green:0.0 blue:0.0 alpha:0.7] set];
199      [pathHRTr fill];
200      //      [[NSColor redColor] set];
201      //      [pathR stroke];
202
203      [[NSColor colorWithDeviceRed:0.0 green:1.0 blue:0.0 alpha:1.0] set];
204      [pathHG stroke];
205      [[NSColor colorWithDeviceRed:0.0 green:1.0 blue:0.0 alpha:0.7] set];
206      [pathHG fill];
207      [[NSColor colorWithDeviceRed:0.0 green:1.0 blue:0.0 alpha:1.0] set];
208      [pathHGTr stroke];
209      [[NSColor colorWithDeviceRed:0.0 green:1.0 blue:0.0 alpha:0.7] set];
210      [pathHGTr fill];
211      //      [[NSColor greenColor] set];
212      //      [pathG stroke];
213
214      [[NSColor colorWithDeviceRed:0.0 green:0.0 blue:1.0 alpha:1.0] set];
215      [pathHB stroke];
216      [[NSColor colorWithDeviceRed:0.0 green:0.0 blue:1.0 alpha:0.7] set];
217      [pathHB fill];
218      [[NSColor colorWithDeviceRed:0.0 green:0.0 blue:1.0 alpha:1.0] set];
219      [pathHBTr stroke];
220      [[NSColor colorWithDeviceRed:0.0 green:0.0 blue:1.0 alpha:0.7] set];
221      [pathHBTr fill];
222      //      [[NSColor blueColor] set];
223      //      [pathB stroke];
224      //  FIXME: temporary, until we have full RGB support
225      [[NSColor blackColor] set];
226      [pathLumi stroke];
227    }
228  else
229    {
230      [[NSColor colorWithDeviceWhite:0.5 alpha:1.0] set];
231      [pathHLumi stroke];
232      [[NSColor colorWithDeviceWhite:0.9 alpha:1.0] set];
233      [pathHLumi fill];
234
235      [[NSColor colorWithDeviceWhite:0.5 alpha:1.0] set];
236      [pathHLumiTr stroke];
237      [[NSColor colorWithDeviceWhite:0.9 alpha:1.0] set];
238      [pathHLumiTr fill];
239
240      [[NSColor blackColor] set];
241      [pathLumi stroke];
242    }
243
244
245
246  /* boundaries */
247  [[NSColor blackColor] set];
248  [NSBezierPath strokeRect: [self bounds]];
249}
250
251-(void)calculateHistogram: (PRImage *)image;
252{
253  NSBitmapImageRep *theImageRep;
254  unsigned char *theData;
255  NSInteger w, h;
256  NSInteger x, y;
257  NSInteger i;
258  NSInteger srcBytesPerPixel;
259  NSInteger srcBytesPerRow;
260  float maxHisto;
261  unsigned viewHeight;
262  float histoScale;
263
264
265  /* get source image representation and associated information */
266  theImageRep = [image bitmapRep];
267
268  w = [theImageRep pixelsWide];
269  h = [theImageRep pixelsHigh];
270  pixNum = h * w;
271  srcBytesPerRow = [theImageRep bytesPerRow];
272  srcBytesPerPixel = [theImageRep bitsPerPixel] / 8;
273  hasColor = [image hasColor];
274
275  theData = [theImageRep bitmapData];
276
277  if (hasColor)
278    {
279      /* calculate the histogram */
280      for (i = 0; i <= UCHAR_MAX; i++)
281	histogramDenormR[i] = histogramDenormG[i] = histogramDenormB[i] = 0;
282      for (y = 0; y < h; y++)
283	for (x = 0; x < w; x++)
284	  {
285	    histogramDenormR[theData[y*srcBytesPerRow + x*srcBytesPerPixel]]++;
286	    histogramDenormG[theData[y*srcBytesPerRow + x*srcBytesPerPixel + 1]]++;
287	    histogramDenormB[theData[y*srcBytesPerRow + x*srcBytesPerPixel + 2]]++;
288	  }
289
290      /* normalize histogram */
291      /* calculate the maximum luminance as maxHisto */
292      maxHisto = 0;
293      for (i = 0; i <= UCHAR_MAX; i++)
294	{
295	  histogramR[i] = (float)histogramDenormR[i] / (float)pixNum;
296	  histogramG[i] = (float)histogramDenormG[i] / (float)pixNum;
297	  histogramB[i] = (float)histogramDenormB[i] / (float)pixNum;
298	  if (histogramR[i] > maxHisto)
299	    maxHisto = histogramR[i];
300	  if (histogramG[i] > maxHisto)
301	    maxHisto = histogramG[i];
302	  if (histogramB[i] > maxHisto)
303	    maxHisto = histogramB[i];
304	}
305    }
306  else
307    {
308      /* calculate the histogram */
309      for (i = 0; i <= UCHAR_MAX; i++)
310        histogramDenormL[i] = 0;
311      for (y = 0; y < h; y++)
312        for (x = 0; x < w; x++)
313          histogramDenormL[theData[y*srcBytesPerRow + x*srcBytesPerPixel]]++;
314
315      /* normalize histogram */
316      maxHisto = 0;
317      for (i = 0; i <= UCHAR_MAX; i++)
318        {
319	  histogramL[i] = (float)histogramDenormL[i] / (float)pixNum;
320	  if (histogramL[i] > maxHisto)
321	    maxHisto = histogramL[i];
322        }
323    }
324
325  viewHeight = 256;
326  NSLog(@"maxHisto L : %f for %u", maxHisto, pixNum);
327  histoScale = (histogramScale * viewHeight) / maxHisto;
328  if (hasColor)
329    {
330      [pathHR removeAllPoints];
331      [pathHG removeAllPoints];
332      [pathHB removeAllPoints];
333      [pathHR moveToPoint: NSMakePoint(0, 0)];
334      [pathHR moveToPoint: NSMakePoint(0, 0)];
335      [pathHG moveToPoint: NSMakePoint(0, 0)];
336      [pathHB moveToPoint: NSMakePoint(0, 0)];
337      for (i = 0; i <= UCHAR_MAX; i++)
338	{
339	  [pathHR lineToPoint: NSMakePoint(i, histogramR[i] * histoScale)];
340	  [pathHG lineToPoint: NSMakePoint(i, histogramG[i] * histoScale)];
341	  [pathHB lineToPoint: NSMakePoint(i, histogramB[i] * histoScale)];
342	}
343      [pathHR lineToPoint: NSMakePoint(viewHeight, 0)];
344      [pathHG lineToPoint: NSMakePoint(viewHeight, 0)];
345      [pathHB lineToPoint: NSMakePoint(viewHeight, 0)];
346    }
347  else
348    {
349      [pathHLumi removeAllPoints];
350      [pathHLumi moveToPoint: NSMakePoint(0, 0)];
351      for (i = 0; i <= UCHAR_MAX; i++)
352	{
353	  [pathHLumi lineToPoint: NSMakePoint(i, histogramL[i] * histoScale)];
354	}
355      [pathHLumi lineToPoint: NSMakePoint(viewHeight, 0)];
356    }
357
358  NSLog(@"curves histogram initing ended");
359}
360
361-(void)calculateFunctions
362{
363  unsigned i;
364  NSArray *curveL;
365
366  curveL = [pathLumi values];
367  for (i = 0; i <= UCHAR_MAX; i++)
368    funL[i] = floor([[curveL objectAtIndex: i] floatValue]);
369}
370
371-(void)calculateTransformedHistograms
372{
373  unsigned long int histogramDenormLTr[UCHAR_MAX+1]; /* not normalized pixel count for each level */
374  unsigned long int histogramDenormRTr[UCHAR_MAX+1]; /* not normalized pixel count for each level */
375  unsigned long int histogramDenormGTr[UCHAR_MAX+1]; /* not normalized pixel count for each level */
376  unsigned long int histogramDenormBTr[UCHAR_MAX+1]; /* not normalized pixel count for each level */
377  unsigned i;
378  float maxHisto;
379  unsigned viewHeight;
380  float histoScale;
381
382  [self calculateFunctions];
383
384  /* zero histogram */
385  for (i = 0; i <= UCHAR_MAX; i++)
386    {
387      histogramDenormLTr[i] = 0;
388      histogramDenormRTr[i] = 0;
389      histogramDenormGTr[i] = 0;
390      histogramDenormBTr[i] = 0;
391    }
392
393  /* derive new histogram */
394  for (i = 0; i <= UCHAR_MAX; i++)
395    {
396      histogramDenormLTr[funL[i]] += histogramDenormL[i];
397      histogramDenormRTr[funL[i]] += histogramDenormR[i];
398      histogramDenormGTr[funL[i]] += histogramDenormG[i];
399      histogramDenormBTr[funL[i]] += histogramDenormB[i];
400    }
401
402  /* normalize histogram */
403  if (hasColor)
404    {
405      maxHisto = 0;
406      for (i = 0; i <= UCHAR_MAX; i++)
407	{
408	  histogramRTr[i] = (float)histogramDenormRTr[i] / (float)pixNum;
409	  histogramGTr[i] = (float)histogramDenormGTr[i] / (float)pixNum;
410	  histogramBTr[i] = (float)histogramDenormBTr[i] / (float)pixNum;
411	  if (histogramRTr[i] > maxHisto)
412	    maxHisto = histogramRTr[i];
413	  if (histogramGTr[i] > maxHisto)
414	    maxHisto = histogramGTr[i];
415	  if (histogramBTr[i] > maxHisto)
416	    maxHisto = histogramBTr[i];
417	}
418    }
419  else
420    {
421      maxHisto = 0;
422      for (i = 0; i <= UCHAR_MAX; i++)
423	{
424	  histogramLTr[i] = (float)histogramDenormLTr[i] / (float)pixNum;
425	  if (histogramLTr[i] > maxHisto)
426	    maxHisto = histogramLTr[i];
427	}
428    }
429
430  viewHeight = 256;
431  NSLog(@"transformed maxHisto L : %f for %u", maxHisto, pixNum);
432  histoScale =  (histogramScale * viewHeight) / maxHisto;
433  [pathHLumiTr removeAllPoints];
434  [pathHLumiTr moveToPoint: NSMakePoint(0, viewHeight)];
435  for (i = 0; i <= UCHAR_MAX; i++)
436    {
437      [pathHLumiTr lineToPoint: NSMakePoint(i, viewHeight-(histogramLTr[i] * histoScale))];
438    }
439  [pathHLumiTr lineToPoint: NSMakePoint(viewHeight, viewHeight)];
440
441  [pathHRTr removeAllPoints];
442  [pathHGTr removeAllPoints];
443  [pathHBTr removeAllPoints];
444  [pathHRTr moveToPoint: NSMakePoint(0, viewHeight)];
445  [pathHGTr moveToPoint: NSMakePoint(0, viewHeight)];
446  [pathHBTr moveToPoint: NSMakePoint(0, viewHeight)];
447  for (i = 0; i <= UCHAR_MAX; i++)
448    {
449      [pathHRTr lineToPoint: NSMakePoint(i, viewHeight - (histogramRTr[i] * histoScale))];
450      [pathHGTr lineToPoint: NSMakePoint(i, viewHeight - (histogramGTr[i] * histoScale))];
451      [pathHBTr lineToPoint: NSMakePoint(i, viewHeight - (histogramBTr[i] * histoScale))];
452    }
453  [pathHRTr lineToPoint: NSMakePoint(viewHeight, viewHeight)];
454  [pathHGTr lineToPoint: NSMakePoint(viewHeight, viewHeight)];
455  [pathHBTr lineToPoint: NSMakePoint(viewHeight, viewHeight)];
456}
457
458- (void)moveControlPoint:(NSPoint)cp ofCurve:(PRCurvesPath *)curveP
459{
460  NSEvent *nextEvent;
461  NSPoint oldP;
462
463  oldP = cp;
464  nextEvent = [[self window] nextEventMatchingMask:
465                               NSLeftMouseUpMask | NSLeftMouseDraggedMask];
466  if([nextEvent type] == NSLeftMouseDragged)
467    {
468      NSPoint p;
469
470      do
471        {
472          p = [nextEvent locationInWindow];
473          p = [self convertPoint:p fromView:nil];
474
475          /* bounds checking */
476          if (p.x < 0)
477            p.x = 0;
478          if (p.x > [self frame].size.width-1)
479            p.x = [self frame].size.width-1;
480          if (p.y < 0)
481            p.y = 0;
482          if (p.y > [self frame].size.height-1)
483            p.y = [self frame].size.height-1;
484
485          [curveP move:oldP toPoint:p];
486          [controller recalculateCurves:self];
487          [controller setBlackPointValue: (int)floor([curveP blackPoint].x)];
488          [controller setWhitePointValue: (int)floor([curveP whitePoint].x)];
489          [self setNeedsDisplay:YES];
490
491          oldP = p;
492          nextEvent = [[self window] nextEventMatchingMask:
493                                       NSLeftMouseUpMask | NSLeftMouseDraggedMask];
494
495        }
496      while([nextEvent type] != NSLeftMouseUp);
497    }
498  [controller parametersChanged:self];
499}
500
501- (void)mouseDown:(NSEvent *)theEvent
502{
503  NSPoint p;
504  unsigned count = [theEvent clickCount];
505
506  p = [theEvent locationInWindow];
507  p = [self convertPoint: p fromView: nil];
508
509  if (count == 1)
510    {
511      if ([pathLumi pointOnControlHandle:p])
512        {
513          NSLog(@"Luminance path hit");
514          [self moveControlPoint:p ofCurve:pathLumi];
515        }
516      else if ([pathR pointOnControlHandle:p])
517        {
518          NSLog(@"Red path hit");
519          [self moveControlPoint:p ofCurve:pathR];
520        }
521      else if ([pathG pointOnControlHandle:p])
522        {
523          NSLog(@"Green path hit");
524          [self moveControlPoint:p ofCurve:pathG];
525        }
526      else if ([pathB pointOnControlHandle:p])
527        {
528          NSLog(@"Blue path hit");
529          [self moveControlPoint:p ofCurve:pathB];
530        }
531    }
532  else
533    {
534      NSLog(@"click count: %d", count);
535    }
536}
537
538@end
539