1/* GSWheelColorPicker.m
2
3   Copyright (C) 2001 Free Software Foundation, Inc.
4
5   Author:  Fred Kiefer <FredKiefer@gmx.de>
6   Date: Febuary 2001
7   Author: Alexander Malmberg <alexander@malmberg.org>
8   Date: May 2002
9
10   This file is part of GNUstep.
11
12   This library is free software; you can redistribute it and/or
13   modify it under the terms of the GNU Lesser General Public
14   License as published by the Free Software Foundation; either
15   version 2 of the License, or (at your option) any later version.
16
17   This library is distributed in the hope that it will be useful,
18   but WITHOUT ANY WARRANTY; without even the implied warranty of
19   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
20   Lesser General Public License for more details.
21
22   You should have received a copy of the GNU Lesser General Public
23   License along with this library; see the file COPYING.LIB.
24   If not, see <http://www.gnu.org/licenses/> or write to the
25   Free Software Foundation, 51 Franklin Street, Fifth Floor,
26   Boston, MA 02110-1301, USA.
27*/
28
29#include <math.h>
30
31#ifndef PI
32#define PI 3.141592653589793
33#endif
34
35#include <Foundation/Foundation.h>
36#include <AppKit/AppKit.h>
37#include <GNUstepGUI/GSHbox.h>
38#include "GSStandardColorPicker.h"
39
40@interface GSColorWheelMarker : NSView
41{
42}
43
44@end
45
46@implementation GSColorWheelMarker : NSView
47
48-(BOOL) isOpaque
49{
50  return YES;
51}
52
53-(void) drawRect: (NSRect)rect
54{
55  NSRect bounds = [self bounds];
56  [[NSColor whiteColor] set];
57  NSRectFill(bounds);
58  [[NSColor blackColor] set];
59  NSFrameRect(bounds);
60}
61
62@end
63
64
65@interface GSColorWheel : NSView
66{
67  float hue, saturation, brightness;
68
69  id target;
70  SEL action;
71
72  GSColorWheelMarker *marker;
73  NSImage *image;
74}
75
76-(float) hue;
77-(float) saturation;
78
79-(void) regenerateImage;
80-(NSRect) markerRect;
81
82-(void) setHue: (float)h saturation: (float)s brightness: (float)brightness;
83
84-(void) setTarget: (id)t;
85-(void) setAction: (SEL)a;
86
87@end
88
89@implementation GSColorWheel
90
91-(id) initWithFrame: (NSRect)frame
92{
93  self = [super initWithFrame: frame];
94  if (nil == self)
95    {
96      return nil;
97    }
98
99  [self setPostsFrameChangedNotifications: YES];
100  [[NSNotificationCenter defaultCenter]
101    addObserver: self
102       selector: @selector(_frameChanged:)
103	   name: NSViewFrameDidChangeNotification
104	 object: self];
105
106  return self;
107}
108
109-(void) _frameChanged: (id)sender
110{
111  [self regenerateImage];
112  [marker setFrame: [self markerRect]];
113}
114
115-(void) dealloc
116{
117  [image release];
118  [[NSNotificationCenter defaultCenter] removeObserver: self];
119  [super dealloc];
120}
121
122-(void) setTarget: (id)t
123{
124  target = t;
125}
126-(void) setAction: (SEL)a
127{
128  action = a;
129}
130
131-(float) hue
132{
133  return hue;
134}
135-(float) saturation
136{
137  return saturation;
138}
139
140-(NSRect) markerRect
141{
142  NSRect frame = [self bounds];
143  float a,r,x,y,cr,cx,cy;
144
145  cx = (frame.origin.x + frame.size.width) / 2;
146  cy = (frame.origin.y + frame.size.height) / 2;
147
148  cr = frame.size.width;
149  if (cr > frame.size.height)
150    cr = frame.size.height;
151
152  cr = cr / 2 - 2;
153
154  a = hue * 2 * PI;
155  r = saturation * cr;
156
157  x = cos(a) * r + cx;
158  y = sin(a) * r + cy;
159
160  return NSMakeRect(x-2,y-2,4,4);
161}
162
163-(void) setHue: (float)h saturation: (float)s brightness: (float)b
164{
165  if (nil == marker)
166    {
167      marker = [[[GSColorWheelMarker alloc] initWithFrame: [self markerRect]] autorelease];
168      [self addSubview: marker];
169    }
170
171  if (hue != h || saturation != s || brightness != b)
172    {
173      BOOL regenerate = (brightness != b);
174
175      hue = h;
176      saturation = s;
177      brightness = b;
178
179      if (regenerate)
180	[self regenerateImage];
181
182      [marker setFrame: [self markerRect]];
183
184      [self setNeedsDisplay: YES];
185    }
186}
187
188-(void) regenerateImage
189{
190  NSSize size = [self convertSizeToBase: [self bounds].size];
191  CGFloat cx, cy, cr;
192
193  [image release];
194  image = nil;
195
196  cx = (size.width) / 2;
197  cy = (size.height) / 2;
198
199  cr = size.width;
200  if (cr > size.height)
201    cr = size.height;
202
203  cr = cr / 2 - 2;
204
205  {
206    NSUInteger width = size.width;
207    NSUInteger height = size.height;
208    NSUInteger bytesPerRow;
209    NSBitmapImageRep *bmp;
210    unsigned char *data;
211    NSUInteger x, y;
212
213    if (width < 1 || height < 1)
214      return;
215
216    bmp = [[NSBitmapImageRep alloc]
217			      initWithBitmapDataPlanes: NULL
218					    pixelsWide: width
219					    pixelsHigh: height
220					 bitsPerSample: 8
221				       samplesPerPixel: 4
222					      hasAlpha: YES
223					      isPlanar: NO
224					colorSpaceName: NSCalibratedRGBColorSpace
225					   bytesPerRow: 0
226					  bitsPerPixel: 32];
227
228    bytesPerRow = [bmp bytesPerRow];
229    data = [bmp bitmapData];
230
231    for (y = 0; y < height; y++)
232      {
233	uint32_t *row = (uint32_t*)(data + (y * bytesPerRow));
234
235	for (x = 0; x < width; x++)
236	  {
237	    CGFloat dx, dy, dist;
238	    CGFloat h, s, v;
239	    CGFloat R, G, B, A;
240
241	    dx = x - cx;
242	    dy = cy - y; // compensate for flipped coordinates
243	    dist = sqrt(dx * dx + dy * dy);
244
245	    // calculate h,s,v from x,y
246	    {
247	      h = atan2(dy, dx) / 2.0 / PI;
248	      if (h < 0)
249		h += 1;
250
251	      s = dist/cr;
252	      if (s > 1)
253		s = 1;
254
255	      v = brightness;
256	    }
257
258	    // calculate R,G,B from h,s,v
259	    {
260	      int	I = (int)(h * 6);
261	      CGFloat V = v;
262	      CGFloat S = s;
263	      CGFloat F = (h * 6) - I;
264	      CGFloat M = V * (1 - S);
265	      CGFloat N = V * (1 - S * F);
266	      CGFloat K = M - N + V;
267
268	      switch (I)
269		{
270		default: R = V; G = K; B = M; break;
271		case 1: R = N; G = V; B = M; break;
272		case 2: R = M; G = V; B = K; break;
273		case 3: R = M; G = N; B = V; break;
274		case 4: R = K; G = M; B = V; break;
275		case 5: R = V; G = M; B = N; break;
276		}
277	    }
278
279	    // calculate alpha
280	    {
281	      A = (cr - dist) + 0.5;
282	      if (A > 1) A = 1;
283	      if (A < 0) A = 0;
284	    }
285
286	    // premultiply color with alpha
287	    R *= A;
288	    G *= A;
289	    B *= A;
290
291	    // store pixel
292#if GS_WORDS_BIGENDIAN
293	    row[x] = ((uint32_t)(255 * R) << 24)
294	      | (((uint32_t)(255 * G)) << 16)
295	      | (((uint32_t)(255 * B)) << 8)
296	      | (((uint32_t)(255 * A)));
297#else
298	    row[x] = ((uint32_t)(255 * R))
299	      | (((uint32_t)(255 * G)) << 8)
300	      | (((uint32_t)(255 * B)) << 16)
301	      | (((uint32_t)(255 * A)) << 24);
302#endif
303	  }
304      }
305
306    image = [[NSImage alloc] initWithSize: [self bounds].size];
307    [image addRepresentation: bmp];
308    [bmp release];
309  }
310}
311
312-(void) drawRect: (NSRect)rect
313{
314  if (nil == image)
315    {
316      [self regenerateImage];
317    }
318
319  [image drawInRect: [self bounds]
320           fromRect: NSZeroRect
321          operation: NSCompositeSourceOver
322           fraction: 1.0];
323}
324
325-(BOOL) acceptsFirstMouse: (NSEvent *)theEvent
326{
327  return YES;
328}
329
330-(BOOL) acceptsFirstResponder
331{
332  return NO;
333}
334
335-(void) handleMouseAtPoint: (NSPoint)point
336{
337  NSRect frame = [self bounds];
338  CGFloat cx, cy, cr, dx, dy, new_hue, new_saturation;
339
340  cx = (frame.origin.x + frame.size.width) / 2;
341  cy = (frame.origin.y + frame.size.height) / 2;
342  cr = frame.size.width;
343  if (cr > frame.size.height)
344    cr = frame.size.height;
345  cr = cr / 2 - 2;
346
347  dx = point.x - cx;
348  dy = point.y - cy;
349
350  new_saturation = sqrt(dx * dx + dy * dy) / cr;
351  if (new_saturation > 1)
352    new_saturation = 1;
353
354  new_hue = atan2(dy, dx) / 2.0 / PI;
355  if (new_hue < 0)
356    new_hue += 1;
357
358  [self setHue: new_hue saturation: new_saturation brightness: brightness];
359
360  if (target)
361    {
362      [target performSelector: action withObject: self];
363    }
364}
365
366-(void) mouseDown: (NSEvent *)theEvent
367{
368  if ([theEvent type] == NSLeftMouseDown)
369    {
370      [self handleMouseAtPoint: [self convertPoint: [theEvent locationInWindow] fromView: nil]];
371    }
372}
373
374-(void) mouseDragged: (NSEvent *)theEvent
375{
376  if ([theEvent type] == NSLeftMouseDragged)
377    {
378      [self handleMouseAtPoint: [self convertPoint: [theEvent locationInWindow] fromView: nil]];
379    }
380}
381
382@end
383
384
385@interface GSWheelColorPicker: NSColorPicker <NSColorPickingCustom>
386{
387  GSHbox *baseView;
388  NSSlider *brightnessSlider;
389  GSColorWheel *wheel;
390}
391
392- (void) sliderChanged: (id) sender;
393- (void) loadViews;
394
395@end
396
397@implementation GSWheelColorPicker
398
399- (void) dealloc
400{
401  RELEASE(baseView);
402  [super dealloc];
403}
404
405- (id)initWithPickerMask:(int)aMask
406	      colorPanel:(NSColorPanel *)colorPanel
407{
408  if (aMask & NSColorPanelWheelModeMask)
409    return [super initWithPickerMask: aMask
410		  colorPanel: colorPanel];
411  RELEASE(self);
412  return nil;
413}
414
415- (int)currentMode
416{
417  return NSWheelModeColorPanel;
418}
419
420- (BOOL)supportsMode:(int)mode
421{
422  return mode == NSWheelModeColorPanel;
423}
424
425- (NSView *)provideNewView:(BOOL)initialRequest
426{
427  if (initialRequest)
428    {
429      [self loadViews];
430    }
431  return baseView;
432}
433
434- (void)setColor:(NSColor *)color
435{
436  CGFloat hue, saturation, brightness, alpha;
437  NSColor *c;
438
439  c = [color colorUsingColorSpaceName: NSCalibratedRGBColorSpace];
440  [c getHue: &hue saturation: &saturation brightness: &brightness alpha: &alpha];
441
442  [(GSColorSliderCell *)[brightnessSlider cell]
443    _setColorSliderCellValues: hue : saturation : brightness];
444  [brightnessSlider setNeedsDisplay: YES];
445  [brightnessSlider setFloatValue: brightness];
446  [wheel setHue: hue saturation: saturation brightness: brightness];
447}
448
449- (void) loadViews
450{
451  NSSlider *s;
452  NSCell *c;
453
454  baseView = [[GSHbox alloc] init];
455  [baseView setAutoresizingMask: (NSViewWidthSizable | NSViewHeightSizable)];
456
457  wheel = [[GSColorWheel alloc] init];
458  [wheel setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
459  [wheel setTarget: self];
460  [wheel setAction: @selector(sliderChanged:)];
461  [baseView addView: wheel];
462
463  s = brightnessSlider = [[NSSlider alloc] initWithFrame: NSMakeRect(0,0,16,0)];
464  [s setAutoresizingMask: NSViewHeightSizable];
465  c = [[GSColorSliderCell alloc] init];
466  [s setCell: c];
467  RELEASE(c);
468  [(GSColorSliderCell *)[s cell] _setColorSliderCellMode: 10];
469  [s setContinuous: YES];
470  [s setMinValue: 0.0];
471  [s setMaxValue: 1.0];
472  [s setTarget: self];
473  [s setAction: @selector(sliderChanged:)];
474  [[s cell] setBezeled: YES];
475
476  [baseView addView: brightnessSlider enablingXResizing: NO];
477}
478
479- (void) sliderChanged: (id) sender
480{
481  float brightness = [brightnessSlider floatValue];
482  float hue = [wheel hue];
483  float saturation = [wheel saturation];
484  float alpha = [_colorPanel alpha];
485  NSColor *c;
486
487  [(GSColorSliderCell *)[brightnessSlider cell]
488    _setColorSliderCellValues: hue : saturation : brightness];
489  [brightnessSlider setNeedsDisplay: YES];
490
491  c = [NSColor colorWithCalibratedHue: hue
492			saturation: saturation
493			brightness: brightness
494			alpha: alpha];
495  [_colorPanel setColor: c];
496}
497
498@end
499
500