Mac OS X ambilight
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AppDelegate.m 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. //
  2. // AppDelegate.m
  3. // DisplayBacklight
  4. //
  5. // Created by Thomas Buck on 21.12.15.
  6. // Copyright © 2015 xythobuz. All rights reserved.
  7. //
  8. #import "AppDelegate.h"
  9. #import "Serial.h"
  10. #import "Screenshot.h"
  11. // ----------------------- Config starts here -----------------------
  12. // The idea behind this algorithm is very simple. It assumes that each LED strand
  13. // follows one edge of one of your displays. So one of the two coordinates should
  14. // always be zero or the width / height of your display.
  15. // Define the amount of LEDs in your strip here
  16. #define LED_COUNT 156
  17. // This defines how large the averaging-boxes should be in the dimension perpendicular
  18. // to the strand. So eg. for a bottom strand, how high the box should be in px.
  19. #define COLOR_AVERAGE_OTHER_DIMENSION_SIZE 100
  20. // Identify your displays here. Currently they're only distinguished by their resolution.
  21. // The ID will be the index in the list, so the first entry is display 0 and so on.
  22. struct DisplayAssignment displays[] = {
  23. { 1920, 1080 },
  24. { 900, 1600 }
  25. };
  26. // This defines the orientation and placement of your strands and is the most important part.
  27. // It begins with the LED IDs this strand includes, starting with ID 0 up to LED_COUNT - 1.
  28. // The second item is the length of this strip, as in the count of LEDs in it.
  29. // The third item is the display ID, defined by the previous struct.
  30. // The fourth and fifth items are the starting X and Y coordinates of the strand.
  31. // As described above, one should always be zero or the display width / height.
  32. // The sixth element is the direction the strand goes (no diagonals supported yet).
  33. // The last element is the size of the averaging-box for each LED, moving with the strand.
  34. // So, if your strand contains 33 LEDs and spans 1920 pixels, this should be (1920 / 33).
  35. // By default you can always use (length in pixel / LED count) for the last item, except
  36. // if your strand does not span the whole length of this screen edge.
  37. //
  38. // For example, this is my personal dual-monitor home setup. The strand starts at the 0
  39. // in the bottom-right of D1, then goes left arount D1, and from there around D2 back
  40. // to the start.
  41. //
  42. // 29
  43. // |------------|
  44. // 5 | /|\ --> -->|
  45. // 33 | | |
  46. // |---------------------------| | |
  47. // |--> --> --> --> -->| \|/ |
  48. // | | |
  49. // 19 | /|\ D1 | D2 | 48
  50. // | | 1920x1080 | 900x1600 |
  51. // | | |
  52. // |<-- <-- <-- <-- <--| 0 | |
  53. // |---------------------------| /|\ \|/ |
  54. // 33 | | |
  55. // 4 | <-- <-- |
  56. // |------------|
  57. // 29
  58. struct LEDStrand strands[] = {
  59. { 0, 33, 0, 1920, 1080, DIR_LEFT, 1920 / 33 },
  60. { 33, 19, 0, 0, 1080, DIR_UP, 1080 / 19 },
  61. { 52, 33, 0, 0, 0, DIR_RIGHT, 1920 / 33 },
  62. { 85, 5, 1, 0, 250, DIR_UP, 250 / 5 },
  63. { 90, 17, 1, 0, 0, DIR_RIGHT, 900 / 17 },
  64. { 107, 28, 1, 900, 0, DIR_DOWN, 1600 / 28 },
  65. { 135, 17, 1, 900, 1600, DIR_LEFT, 900 / 17 },
  66. { 152, 4, 1, 0, 1600, DIR_UP, 180 / 4 }
  67. };
  68. // This defines the update-speed of the Ambilight, in seconds.
  69. // With a baudrate of 115200 and 156 LEDs and 14-bytes Magic-Word,
  70. // theoretically you could transmit:
  71. // 115200 / ((14 + (156 * 3)) * 8) =~ 30 Frames per Second
  72. // Inserting (1.0 / 30.0) here would try to reach these 30FPS,
  73. // but will probably cause high CPU-Usage.
  74. // (Run-Time of the algorithm is ignored here, so real speed will be
  75. // slightly lower.)
  76. #define DISPLAY_DELAY (1.0 / 30.0)
  77. // How many pixels to skip when calculating the average color.
  78. // Slightly increases performance and doesn't really alter the result.
  79. #define AVERAGE_PIXEL_SKIP 2
  80. // Magic identifying string used to differntiate start of packets.
  81. // Has to be the same here and in the Arduino Sketch.
  82. #define MAGIC_WORD @"xythobuzRGBled"
  83. // These are the values stored persistently in the preferences
  84. #define PREF_SERIAL_PORT @"SerialPort"
  85. #define PREF_BRIGHTNESS @"Brightness"
  86. #define PREF_TURNED_ON @"IsEnabled"
  87. // If this is defined it will print the FPS every DEBUG_PRINT_FPS seconds
  88. //#define DEBUG_PRINT_FPS 10
  89. // When grabbing screenshots the resulting picture will not have any color filtering
  90. // effects applied, as is the case when using software like f.lux that makes the colors
  91. // warmer at night. So I've added the algorithm to 'warm up' the colors myself. Set
  92. // the target temperature in Kelvin here, it should be the same as in f.lux.
  93. // ToDo Change color-temperature depending on time of day to match f.lux adjustments
  94. #define TARGET_COLOR_TEMPERATURE 2800.0
  95. // ------------------------ Config ends here ------------------------
  96. @interface AppDelegate ()
  97. @property (weak) IBOutlet NSMenu *statusMenu;
  98. @property (weak) IBOutlet NSMenu *menuPorts;
  99. @property (weak) IBOutlet NSMenuItem *buttonAmbilight;
  100. @property (weak) IBOutlet NSMenuItem *brightnessItem;
  101. @property (weak) IBOutlet NSSlider *brightnessSlider;
  102. @property (weak) IBOutlet NSMenuItem *brightnessLabel;
  103. @property (strong) NSStatusItem *statusItem;
  104. @property (strong) NSImage *statusImage;
  105. @property (strong) NSTimer *timer;
  106. @property (strong) Serial *serial;
  107. @property (strong) NSArray *lastDisplayIDs;
  108. @property (assign) BOOL restartAmbilight;
  109. @end
  110. @implementation AppDelegate
  111. @synthesize statusMenu, application;
  112. @synthesize menuPorts, buttonAmbilight;
  113. @synthesize brightnessItem, brightnessSlider, brightnessLabel;
  114. @synthesize statusItem, statusImage, lastDisplayIDs;
  115. @synthesize timer, serial, restartAmbilight;
  116. - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
  117. serial = [[Serial alloc] init];
  118. timer = nil;
  119. restartAmbilight = NO;
  120. // Set default configuration values
  121. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  122. NSMutableDictionary *appDefaults = [NSMutableDictionary dictionaryWithObject:@"" forKey:PREF_SERIAL_PORT];
  123. [appDefaults setObject:[NSNumber numberWithFloat:50.0] forKey:PREF_BRIGHTNESS];
  124. [appDefaults setObject:[NSNumber numberWithBool:NO] forKey:PREF_TURNED_ON];
  125. [store registerDefaults:appDefaults];
  126. [store synchronize];
  127. // Load existing configuration values
  128. NSString *savedPort = [store stringForKey:PREF_SERIAL_PORT];
  129. float brightness = [store floatForKey:PREF_BRIGHTNESS];
  130. BOOL ambilightIsOn = [store boolForKey:PREF_TURNED_ON];
  131. // Prepare status bar menu
  132. statusImage = [NSImage imageNamed:@"MenuIcon"];
  133. statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
  134. [statusImage setTemplate:YES];
  135. [statusItem setImage:statusImage];
  136. [statusItem setMenu:statusMenu];
  137. // Prepare brightness menu
  138. brightnessItem.view = brightnessSlider;
  139. [brightnessSlider setFloatValue:brightness];
  140. [brightnessLabel setTitle:[NSString stringWithFormat:@"Value: %.0f%%", brightness]];
  141. // Prepare serial port menu
  142. BOOL foundPort = NO;
  143. NSArray *ports = [Serial listSerialPorts];
  144. if ([ports count] > 0) {
  145. [menuPorts removeAllItems];
  146. for (int i = 0; i < [ports count]; i++) {
  147. // Add Menu Item for this port
  148. NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[ports objectAtIndex:i] action:@selector(selectedSerialPort:) keyEquivalent:@""];
  149. [menuPorts addItem:item];
  150. // Set Enabled if it was used the last time
  151. if ((savedPort != nil) && [[ports objectAtIndex:i] isEqualToString:savedPort]) {
  152. // Try to open serial port
  153. [serial setPortName:savedPort];
  154. if (![serial openPort]) {
  155. foundPort = YES;
  156. [[menuPorts itemAtIndex:i] setState:NSOnState];
  157. }
  158. }
  159. }
  160. if (!foundPort) {
  161. // I'm using a cheap chinese Arduino Nano clone with a CH340 chipset.
  162. // This driver creates device-files in /dev/cu.* that don't correspond
  163. // to the chip-id and change every time the adapter is re-enumerated.
  164. // That means we may have to try and find the device again after the
  165. // stored name does no longer exist. In this case, we simply try the first
  166. // device that starts with /dev/cu.wchusbserial*...
  167. for (int i = 0; i < [ports count]; i++) {
  168. if ([[ports objectAtIndex:i] hasPrefix:@"/dev/cu.wchusbserial"]) {
  169. // Try to open serial port
  170. [serial setPortName:savedPort];
  171. if (![serial openPort]) {
  172. [[menuPorts itemAtIndex:i] setState:NSOnState];
  173. // Reattempt next matching device when opening this one fails.
  174. break;
  175. }
  176. }
  177. }
  178. }
  179. }
  180. [Screenshot init:self];
  181. lastDisplayIDs = [Screenshot listDisplays];
  182. if (ambilightIsOn) {
  183. timer = [NSTimer scheduledTimerWithTimeInterval:DISPLAY_DELAY target:self selector:@selector(visualizeDisplay:) userInfo:nil repeats:NO];
  184. [buttonAmbilight setState:NSOnState];
  185. }
  186. }
  187. - (void)applicationWillTerminate:(NSNotification *)aNotification {
  188. // Stop previous timer setting
  189. if (timer != nil) {
  190. [timer invalidate];
  191. timer = nil;
  192. }
  193. // Remove display callback
  194. [Screenshot close:self];
  195. // Turn off all lights if possible
  196. if ([serial isOpen]) {
  197. [self sendNullFrame];
  198. [serial closePort];
  199. }
  200. }
  201. - (IBAction)relistSerialPorts:(id)sender {
  202. // Refill port list
  203. NSArray *ports = [Serial listSerialPorts];
  204. [menuPorts removeAllItems];
  205. for (int i = 0; i < [ports count]; i++) {
  206. // Add Menu Item for this port
  207. NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[ports objectAtIndex:i] action:@selector(selectedSerialPort:) keyEquivalent:@""];
  208. [menuPorts addItem:item];
  209. // Mark it if it is currently open
  210. if ([serial isOpen]) {
  211. if ([[ports objectAtIndex:i] isEqualToString:[serial portName]]) {
  212. [[menuPorts itemAtIndex:i] setState:NSOnState];
  213. }
  214. }
  215. }
  216. }
  217. - (void)selectedSerialPort:(NSMenuItem *)source {
  218. // Store selection for next start-up
  219. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  220. [store setObject:[source title] forKey:PREF_SERIAL_PORT];
  221. [store synchronize];
  222. // De-select all other ports
  223. for (int i = 0; i < [menuPorts numberOfItems]; i++) {
  224. [[menuPorts itemAtIndex:i] setState:NSOffState];
  225. }
  226. // Select only the current port
  227. [source setState:NSOnState];
  228. // Close previously opened port, if any
  229. if ([serial isOpen]) {
  230. [serial closePort];
  231. }
  232. // Stop previous timer setting
  233. if (timer != nil) {
  234. [timer invalidate];
  235. timer = nil;
  236. }
  237. // Turn off ambilight button
  238. [buttonAmbilight setState:NSOffState];
  239. // Try to open selected port
  240. [serial setPortName:[source title]];
  241. if ([serial openPort] != 0) {
  242. [source setState:NSOffState];
  243. }
  244. }
  245. - (IBAction)toggleAmbilight:(NSMenuItem *)sender {
  246. if ([sender state] == NSOnState) {
  247. [sender setState:NSOffState];
  248. // Stop previous timer setting
  249. if (timer != nil) {
  250. [timer invalidate];
  251. timer = nil;
  252. }
  253. [self sendNullFrame];
  254. // Store state
  255. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  256. [store setObject:[NSNumber numberWithBool:NO] forKey:PREF_TURNED_ON];
  257. [store synchronize];
  258. } else {
  259. [sender setState:NSOnState];
  260. timer = [NSTimer scheduledTimerWithTimeInterval:DISPLAY_DELAY target:self selector:@selector(visualizeDisplay:) userInfo:nil repeats:NO];
  261. // Store state
  262. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  263. [store setObject:[NSNumber numberWithBool:YES] forKey:PREF_TURNED_ON];
  264. [store synchronize];
  265. }
  266. }
  267. - (void)stopAmbilight {
  268. restartAmbilight = NO;
  269. if (timer != nil) {
  270. restartAmbilight = YES;
  271. [timer invalidate];
  272. timer = nil;
  273. [buttonAmbilight setState:NSOffState];
  274. }
  275. }
  276. - (void)newDisplayList:(NSArray *)displayIDs {
  277. lastDisplayIDs = displayIDs;
  278. if (restartAmbilight) {
  279. restartAmbilight = NO;
  280. [buttonAmbilight setState:NSOnState];
  281. timer = [NSTimer scheduledTimerWithTimeInterval:DISPLAY_DELAY target:self selector:@selector(visualizeDisplay:) userInfo:nil repeats:NO];
  282. }
  283. }
  284. - (IBAction)brightnessMoved:(NSSlider *)sender {
  285. [brightnessLabel setTitle:[NSString stringWithFormat:@"Value: %.0f%%", [sender floatValue]]];
  286. // Store changed value in preferences
  287. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  288. [store setObject:[NSNumber numberWithFloat:[sender floatValue]] forKey:PREF_BRIGHTNESS];
  289. [store synchronize];
  290. }
  291. - (IBAction)showAbout:(id)sender {
  292. [NSApp activateIgnoringOtherApps:YES];
  293. [application orderFrontStandardAboutPanel:self];
  294. }
  295. - (void)sendLEDFrame {
  296. if ([serial isOpen]) {
  297. [serial sendString:MAGIC_WORD];
  298. [serial sendData:(char *)ledColorData withLength:(sizeof(ledColorData) / sizeof(ledColorData[0]))];
  299. }
  300. }
  301. - (void)sendNullFrame {
  302. for (int i = 0; i < (sizeof(ledColorData) / sizeof(ledColorData[0])); i++) {
  303. ledColorData[i] = 0;
  304. }
  305. [self sendLEDFrame];
  306. }
  307. // ----------------------------------------------------
  308. // ------------ 'Ambilight' Visualizations ------------
  309. // ----------------------------------------------------
  310. UInt8 ledColorData[LED_COUNT * 3];
  311. - (UInt32)calculateAverage:(unsigned char *)data Width:(NSInteger)width Height:(NSInteger)height SPP:(NSInteger)spp Alpha:(BOOL)alpha StartX:(NSInteger)startX StartY:(NSInteger)startY EndX:(NSInteger)endX EndY:(NSInteger)endY {
  312. int redC = 0, greenC = 1, blueC = 2;
  313. if (alpha) {
  314. redC = 1; greenC = 2; blueC = 3;
  315. }
  316. NSInteger xa, xb, ya, yb;
  317. if (startX < endX) {
  318. xa = startX;
  319. xb = endX;
  320. } else {
  321. xa = endX;
  322. xb = startX;
  323. }
  324. if (startY < endY) {
  325. ya = startY;
  326. yb = endY;
  327. } else {
  328. ya = endY;
  329. yb = startY;
  330. }
  331. unsigned long red = 0, green = 0, blue = 0, count = 0;
  332. for (NSInteger i = xa; i < xb; i += AVERAGE_PIXEL_SKIP) {
  333. for (NSInteger j = ya; j < yb; j++) {
  334. count++;
  335. unsigned long index = i + (j * width);
  336. red += data[(index * spp) + redC];
  337. green += data[(index * spp) + greenC];
  338. blue += data[(index * spp) + blueC];
  339. }
  340. }
  341. red /= count;
  342. green /= count;
  343. blue /= count;
  344. red *= [brightnessSlider floatValue] / 100.0f;
  345. green *= [brightnessSlider floatValue] / 100.0f;
  346. blue *= [brightnessSlider floatValue] / 100.0f;
  347. return ((UInt32)red << 16) | ((UInt32)green << 8) | ((UInt32)blue);
  348. }
  349. - (void)visualizeSingleDisplay:(NSInteger)disp Data:(unsigned char *)data Width:(unsigned long)width Height:(unsigned long)height SPP:(NSInteger)spp Alpha:(BOOL)alpha {
  350. for (int i = 0; i < (sizeof(strands) / sizeof(strands[0])); i++) {
  351. if (strands[i].display == disp) {
  352. // Walk the strand, calculating value for each LED
  353. unsigned long x = strands[i].startX;
  354. unsigned long y = strands[i].startY;
  355. unsigned long blockWidth = COLOR_AVERAGE_OTHER_DIMENSION_SIZE;
  356. unsigned long blockHeight = COLOR_AVERAGE_OTHER_DIMENSION_SIZE;
  357. if ((strands[i].direction == DIR_LEFT) || (strands[i].direction == DIR_RIGHT)) {
  358. blockWidth = strands[i].size;
  359. } else {
  360. blockHeight = strands[i].size;
  361. }
  362. for (int led = strands[i].idMin; led < (strands[i].idMin + strands[i].count); led++) {
  363. // First move appropriately in the direction of the strand
  364. unsigned long endX = x, endY = y;
  365. if (strands[i].direction == DIR_LEFT) {
  366. endX -= blockWidth;
  367. } else if (strands[i].direction == DIR_RIGHT) {
  368. endX += blockWidth;
  369. } else if (strands[i].direction == DIR_UP) {
  370. endY -= blockHeight;
  371. } else if (strands[i].direction == DIR_DOWN) {
  372. endY += blockHeight;
  373. }
  374. // But also span the averaging-square in the other dimension, depending on which
  375. // side of the monitor we're at.
  376. if ((strands[i].direction == DIR_LEFT) || (strands[i].direction == DIR_RIGHT)) {
  377. if (y == 0) {
  378. endY = blockHeight;
  379. } else if (y == displays[disp].height) {
  380. endY -= blockHeight;
  381. }
  382. } else {
  383. if (x == 0) {
  384. endX = blockWidth;
  385. } else if (x == displays[disp].width) {
  386. endX -= blockWidth;
  387. }
  388. }
  389. // Calculate average color for this led
  390. UInt32 color = [self calculateAverage:data Width:width Height:height SPP:spp Alpha:alpha StartX:x StartY:y EndX:endX EndY:endY];
  391. #ifdef TARGET_COLOR_TEMPERATURE
  392. struct Color3 c = { ((color & 0xFF0000) >> 16) / 255.0, ((color & 0x00FF00) >> 8) / 255.0, (color & 0x0000FF) / 255.0 };
  393. c = getRGBfromTemperature(TARGET_COLOR_TEMPERATURE, c);
  394. ledColorData[led * 3] = (int)(c.r * 255.0);
  395. ledColorData[(led * 3) + 1] = (int)(c.g * 255.0);
  396. ledColorData[(led * 3) + 2] = (int)(c.b * 255.0);
  397. #else
  398. ledColorData[led * 3] = (color & 0xFF0000) >> 16;
  399. ledColorData[(led * 3) + 1] = (color & 0x00FF00) >> 8;
  400. ledColorData[(led * 3) + 2] = color & 0x0000FF;
  401. #endif
  402. // Move to next LED
  403. if ((strands[i].direction == DIR_LEFT) || (strands[i].direction == DIR_RIGHT)) {
  404. x = endX;
  405. } else {
  406. y = endY;
  407. }
  408. }
  409. }
  410. }
  411. }
  412. - (void)visualizeDisplay:(NSTimer *)time {
  413. #ifdef DEBUG_PRINT_FPS
  414. static NSInteger frameCount = 0;
  415. static NSDate *lastPrintTime = nil;
  416. if (lastPrintTime == nil) {
  417. lastPrintTime = [NSDate date];
  418. }
  419. #endif
  420. //NSLog(@"Running Ambilight-Algorithm (%lu)...", (unsigned long)[lastDisplayIDs count]);
  421. // Create a Screenshot for all connected displays
  422. for (NSInteger i = 0; i < [lastDisplayIDs count]; i++) {
  423. NSBitmapImageRep *screen = [Screenshot screenshot:[lastDisplayIDs objectAtIndex:i]];
  424. unsigned long width = [screen pixelsWide];
  425. unsigned long height = [screen pixelsHigh];
  426. // Ensure we can handle the format of this display
  427. NSInteger spp = [screen samplesPerPixel];
  428. if (((spp != 3) && (spp != 4)) || ([screen isPlanar] == YES) || ([screen numberOfPlanes] != 1)) {
  429. NSLog(@"Unknown image format for %ld (%ld, %c, %ld)!\n", (long)i, (long)spp, ([screen isPlanar] == YES) ? 'p' : 'n', (long)[screen numberOfPlanes]);
  430. continue;
  431. }
  432. // Find out how the color components are ordered
  433. BOOL alpha = NO;
  434. if ([screen bitmapFormat] & NSAlphaFirstBitmapFormat) {
  435. alpha = YES;
  436. }
  437. // Try to find the matching display id for the strand associations
  438. for (int n = 0; n < (sizeof(displays) / sizeof(displays[0])); n++) {
  439. if ((width == displays[n].width) && (height == displays[n].height)) {
  440. unsigned char *data = [screen bitmapData];
  441. [self visualizeSingleDisplay:n Data:data Width:width Height:height SPP:spp Alpha:alpha];
  442. break;
  443. }
  444. }
  445. }
  446. [self sendLEDFrame];
  447. #ifdef DEBUG_PRINT_FPS
  448. frameCount++;
  449. NSDate *now = [NSDate date];
  450. NSTimeInterval interval = [now timeIntervalSinceDate:lastPrintTime];
  451. if (interval >= DEBUG_PRINT_FPS) {
  452. NSLog(@"FPS: %.2f", frameCount / interval);
  453. frameCount = 0;
  454. lastPrintTime = now;
  455. }
  456. #endif
  457. timer = [NSTimer scheduledTimerWithTimeInterval:DISPLAY_DELAY target:self selector:@selector(visualizeDisplay:) userInfo:nil repeats:NO];
  458. }
  459. // ----------------------------------------------------
  460. // ----------- Color Temperature Adjustment -----------
  461. // ----------------------------------------------------
  462. #define LUMINANCE_PRESERVATION 0.75
  463. #define EPSILON 1e-10
  464. #define SATURATION_FACTOR 0.9
  465. struct Color3 {
  466. float r, g, b;
  467. };
  468. float saturateFloat(float v) {
  469. if (v < 0.0f) {
  470. return 0.0f;
  471. } else if (v > 1.0f) {
  472. return 1.0f;
  473. } else {
  474. return v;
  475. }
  476. }
  477. struct Color3 saturateColor(struct Color3 v) {
  478. v.r = saturateFloat(v.r);
  479. v.g = saturateFloat(v.g);
  480. v.b = saturateFloat(v.b);
  481. return v;
  482. }
  483. struct Color3 colorTemperatureToRGB(float temperatureInKelvins) {
  484. struct Color3 retColor;
  485. if (temperatureInKelvins < 1000.0) {
  486. temperatureInKelvins = 1000.0;
  487. } else if (temperatureInKelvins > 40000) {
  488. temperatureInKelvins = 40000.0;
  489. }
  490. temperatureInKelvins /= 100.0;
  491. if (temperatureInKelvins <= 66.0) {
  492. retColor.r = 1.0;
  493. retColor.g = saturateFloat(0.39008157876901960784 * log(temperatureInKelvins) - 0.63184144378862745098);
  494. } else {
  495. float t = temperatureInKelvins - 60.0;
  496. retColor.r = saturateFloat(1.29293618606274509804 * pow(t, -0.1332047592));
  497. retColor.g = saturateFloat(1.12989086089529411765 * pow(t, -0.0755148492));
  498. }
  499. if (temperatureInKelvins >= 66.0) {
  500. retColor.b = 1.0;
  501. } else if (temperatureInKelvins <= 19.0) {
  502. retColor.b = 0.0;
  503. } else {
  504. retColor.b = saturateFloat(0.54320678911019607843 * log(temperatureInKelvins - 10.0) - 1.19625408914);
  505. }
  506. return retColor;
  507. }
  508. float luminance(struct Color3 color) {
  509. float min = fmin(fmin(color.r, color.g), color.b);
  510. float max = fmax(fmax(color.r, color.g), color.b);
  511. return (max + min) / 2.0;
  512. }
  513. struct Color3 HUEtoRGB(float h) {
  514. float r = fabs(h * 6.0 - 3.0) - 1.0;
  515. float g = 2.0 - fabs(h * 6.0 - 2.0);
  516. float b = 2.0 - fabs(h * 6.0 - 4.0);
  517. struct Color3 ret = { r, g, b };
  518. return saturateColor(ret);
  519. }
  520. struct Color3 HSLtoRGB(struct Color3 hsl) {
  521. struct Color3 rgb = HUEtoRGB(hsl.r);
  522. float c = (1.0 - fabs(2.0 * hsl.b - 1.0)) * hsl.g;
  523. struct Color3 ret = { (rgb.r - 0.5) * c + hsl.b, (rgb.g - 0.5) * c + hsl.b, (rgb.b - 0.5) * c + hsl.b };
  524. return ret;
  525. }
  526. struct Color3 RGBtoHCV(struct Color3 rgb) {
  527. // Based on work by Sam Hocevar and Emil Persson
  528. struct Color3 p;
  529. float pw;
  530. if (rgb.g < rgb.b) {
  531. p.r = rgb.b;
  532. p.g = rgb.g;
  533. p.b = -1.0;
  534. pw = 2.0 / 3.0;
  535. } else {
  536. p.r = rgb.g;
  537. p.g = rgb.b;
  538. p.b = 0.0;
  539. pw = -1.0 / 3.0;
  540. }
  541. struct Color3 q;
  542. float qw;
  543. if (rgb.r < p.r) {
  544. q.r = p.r;
  545. q.g = p.g;
  546. q.b = pw;
  547. qw = rgb.r;
  548. } else {
  549. q.r = rgb.r;
  550. q.g = p.g;
  551. q.b = p.b;
  552. qw = p.r;
  553. }
  554. float c = q.r - fmin(qw, q.g);
  555. float h = fabs((qw - q.g) / (6.0 * c + EPSILON) + q.b);
  556. struct Color3 res = { h, c, q.r };
  557. return res;
  558. }
  559. struct Color3 RGBtoHSL(struct Color3 rgb) {
  560. struct Color3 hcv = RGBtoHCV(rgb);
  561. float l = hcv.b - hcv.g * 0.5;
  562. float s = hcv.g / (1.0 - fabs(l * 2.0 - 1.0) + EPSILON);
  563. struct Color3 res = { hcv.r, s, l };
  564. return res;
  565. }
  566. float mixFloat(float a, float b, float factor) {
  567. return a + ((b - a) * factor);
  568. }
  569. struct Color3 mixColor(struct Color3 a, struct Color3 b, float factor) {
  570. struct Color3 res;
  571. res.r = mixFloat(a.r, b.r, factor);
  572. res.g = mixFloat(a.g, b.g, factor);
  573. res.b = mixFloat(a.b, b.b, factor);
  574. return res;
  575. }
  576. struct Color3 blendColor(struct Color3 a, struct Color3 b) {
  577. struct Color3 res;
  578. res.r = a.r * b.r;
  579. res.g = a.g * b.g;
  580. res.b = a.b * b.b;
  581. return res;
  582. }
  583. // http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
  584. // Given a temperature (in Kelvin), estimate an RGB equivalent
  585. struct Color3 getRGBfromTemperature(float temperature, struct Color3 color) {
  586. struct Color3 tempColor = colorTemperatureToRGB(temperature);
  587. float originalLuminance = luminance(color);
  588. struct Color3 blended = mixColor(color, blendColor(color, tempColor), SATURATION_FACTOR);
  589. struct Color3 resultHSL = RGBtoHSL(blended);
  590. struct Color3 converted = { resultHSL.r, resultHSL.g, originalLuminance };
  591. struct Color3 luminancePreservedRGB = HSLtoRGB(converted);
  592. return mixColor(blended, luminancePreservedRGB, LUMINANCE_PRESERVATION);
  593. }
  594. @end