histogram.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. function generateHistogram(imageElement, canvas) {
  2. try {
  3. // Check if image is loaded and has valid dimensions
  4. if (!imageElement.complete || imageElement.naturalWidth === 0) {
  5. console.log('Image not fully loaded yet');
  6. // Retry after a short delay
  7. setTimeout(() => generateHistogram(imageElement, canvas), 100);
  8. return;
  9. }
  10. console.log('Generating histogram for image:', imageElement.naturalWidth, 'x', imageElement.naturalHeight);
  11. // Create a temporary canvas to get image data
  12. const tempCanvas = document.createElement('canvas');
  13. const tempCtx = tempCanvas.getContext('2d');
  14. tempCanvas.width = imageElement.naturalWidth;
  15. tempCanvas.height = imageElement.naturalHeight;
  16. // Draw the image to the temp canvas
  17. tempCtx.drawImage(imageElement, 0, 0);
  18. // Get image data
  19. const imageData = tempCtx.getImageData(0, 0, imageElement.naturalWidth, imageElement.naturalHeight);
  20. const { width, height, data } = imageData;
  21. const totalPixels = width * height;
  22. const numSamples = Math.min(10000, totalPixels); // sample up to 10,000 pixels
  23. // Initialize histogram arrays for R, G, B
  24. const histR = new Array(256).fill(0);
  25. const histG = new Array(256).fill(0);
  26. const histB = new Array(256).fill(0);
  27. // Sample pixels
  28. let sampleCount = 0;
  29. for (let s = 0; s < numSamples; s++) {
  30. const i = Math.floor(Math.random() * width);
  31. const j = Math.floor(Math.random() * height);
  32. const index = (j * width + i) * 4;
  33. const r = data[index];
  34. const g = data[index + 1];
  35. const b = data[index + 2];
  36. histR[r]++;
  37. histG[g]++;
  38. histB[b]++;
  39. sampleCount++;
  40. }
  41. console.log('Sampled', sampleCount, 'pixels for histogram');
  42. // Draw histogram on canvas
  43. const ctx = canvas.getContext('2d');
  44. const canvasWidth = canvas.width;
  45. const canvasHeight = canvas.height;
  46. ctx.clearRect(0, 0, canvasWidth, canvasHeight);
  47. // Find max value for scaling
  48. const maxR = Math.max(...histR.slice(1, 255));
  49. const maxG = Math.max(...histG.slice(1, 255));
  50. const maxB = Math.max(...histB.slice(1, 255));
  51. const maxVal = Math.max(maxR, maxG, maxB);
  52. if (maxVal === 0) {
  53. // No data to display
  54. ctx.fillStyle = '#666';
  55. ctx.font = '12px Arial';
  56. ctx.textAlign = 'center';
  57. ctx.fillText('No histogram data', canvasWidth / 2, canvasHeight / 2);
  58. console.log('No histogram data to display');
  59. return;
  60. }
  61. const scale = canvasHeight / maxVal;
  62. // Draw bars for each channel with some transparency
  63. for (let i = 1; i < 255; i+=2) {
  64. // Red channel
  65. ctx.fillStyle = 'rgba(255, 100, 100, 0.7)';
  66. ctx.fillRect(i * (canvasWidth / 256), canvasHeight - histR[i] * scale, canvasWidth / 256, histR[i] * scale);
  67. // Green channel
  68. ctx.fillStyle = 'rgba(100, 255, 100, 0.7)';
  69. ctx.fillRect(i * (canvasWidth / 256), canvasHeight - histG[i] * scale, canvasWidth / 256, histG[i] * scale);
  70. // Blue channel
  71. ctx.fillStyle = 'rgba(100, 100, 255, 0.7)';
  72. ctx.fillRect(i * (canvasWidth / 256), canvasHeight - histB[i] * scale, canvasWidth / 256, histB[i] * scale);
  73. }
  74. // Add a subtle border
  75. ctx.strokeStyle = '#555';
  76. ctx.lineWidth = 1;
  77. ctx.strokeRect(0, 0, canvasWidth, canvasHeight);
  78. console.log('Histogram generated successfully');
  79. } catch (error) {
  80. console.error('Error generating histogram:', error);
  81. const ctx = canvas.getContext('2d');
  82. const canvasWidth = canvas.width;
  83. const canvasHeight = canvas.height;
  84. ctx.clearRect(0, 0, canvasWidth, canvasHeight);
  85. ctx.fillStyle = '#666';
  86. ctx.font = '12px Arial';
  87. ctx.textAlign = 'center';
  88. ctx.fillText('Histogram unavailable', canvasWidth / 2, canvasHeight / 2);
  89. }
  90. }
  91. function analysis_tone_types(imageElement, callback) {
  92. try {
  93. // Check if image is loaded and has valid dimensions
  94. if (!imageElement.complete || imageElement.naturalWidth === 0) {
  95. console.log('Image not fully loaded yet');
  96. // Retry after a short delay
  97. setTimeout(() => analysis_tone_types(imageElement, callback), 100);
  98. return;
  99. }
  100. console.log('Analyzing tone for image:', imageElement.naturalWidth, 'x', imageElement.naturalHeight);
  101. // Create a temporary canvas to get image data
  102. const tempCanvas = document.createElement('canvas');
  103. const tempCtx = tempCanvas.getContext('2d');
  104. tempCanvas.width = imageElement.naturalWidth;
  105. tempCanvas.height = imageElement.naturalHeight;
  106. // Draw the image to the temp canvas
  107. tempCtx.drawImage(imageElement, 0, 0);
  108. // Get image data
  109. const imageData = tempCtx.getImageData(0, 0, imageElement.naturalWidth, imageElement.naturalHeight);
  110. const { width, height, data } = imageData;
  111. const totalPixels = width * height;
  112. // Thresholds for shadows and highlights (in luminance 0-255)
  113. const shadowThreshold = 0.1 * 255; // 10%
  114. const highlightThreshold = 0.9 * 255; // 90%
  115. let totalLuminance = 0;
  116. let minLum = 255;
  117. let maxLum = 0;
  118. let shadowCount = 0;
  119. let highlightCount = 0;
  120. // Calculate luminance for each pixel
  121. for (let i = 0; i < data.length; i += 4) {
  122. const r = data[i];
  123. const g = data[i + 1];
  124. const b = data[i + 2];
  125. const lum = 0.299 * r + 0.587 * g + 0.114 * b;
  126. totalLuminance += lum;
  127. if (lum < minLum) minLum = lum;
  128. if (lum > maxLum) maxLum = lum;
  129. if (lum < shadowThreshold) shadowCount++;
  130. if (lum > highlightThreshold) highlightCount++;
  131. }
  132. // Brightness: average luminance as percentage
  133. const avgLuminance = totalLuminance / totalPixels;
  134. const brightness = (avgLuminance / 255) * 100;
  135. // Contrast: range-based as percentage
  136. const contrast = ((maxLum - minLum) / 255) * 100;
  137. // Shadow and highlight ratios
  138. const shadowRatio = (shadowCount / totalPixels) * 100;
  139. const highlightRatio = (highlightCount / totalPixels) * 100;
  140. // Return results to callback
  141. callback({
  142. brightness: Math.round(brightness) + "%",
  143. contrast: Math.round(contrast) + "%",
  144. shadowRatio: Math.round(shadowRatio) + "%",
  145. highlightRatio: Math.round(highlightRatio) + "%"
  146. });
  147. console.log('Tone analysis completed');
  148. } catch (error) {
  149. console.error('Error in analysis_tone_types:', error);
  150. callback(null);
  151. }
  152. }
  153. function get_tone_type(brightness, contrast, shadowRatio, highlightRatio) {
  154. const b = parseFloat(brightness);
  155. const c = parseFloat(contrast);
  156. const s = parseFloat(shadowRatio);
  157. const h = parseFloat(highlightRatio);
  158. // Classify based on brightness (Key)
  159. let key;
  160. if (b > 65) {
  161. key = "High Key";
  162. } else if (b < 35) {
  163. key = "Low Key";
  164. } else {
  165. key = "Middle Key";
  166. }
  167. // Classify based on contrast (Scale)
  168. let scale;
  169. if (c > 70) {
  170. scale = "Long Scale";
  171. } else if (c < 30) {
  172. scale = "Short Scale";
  173. } else {
  174. scale = "Middle Scale";
  175. }
  176. return key + " " + scale;
  177. }