1
0

image2sand-init.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. /**
  2. * Image2Sand (https://github.com/orionwc/Image2Sand) Initialization
  3. *
  4. * This script handles the initialization of the Image2Sand converter.
  5. *
  6. */
  7. // Global variables for the image converter
  8. let originalImage = null;
  9. let fileName = '';
  10. let convertedCoordinates = null;
  11. let currentImageData = null;
  12. /**
  13. * Open the image converter dialog with the selected image
  14. * @param {File} file - The image file to convert
  15. */
  16. function openImageConverter(file) {
  17. if (!file) {
  18. logMessage('No file selected for conversion.', LOG_TYPE.ERROR);
  19. return;
  20. }
  21. // Check if the file is an image
  22. if (!file.type.startsWith('image/')) {
  23. // If not an image, let the original uploadThetaRho handle it
  24. return;
  25. }
  26. fileName = file.name.split('.')[0]; // Remove extension
  27. // Create an image element to load the file
  28. const img = new Image();
  29. img.onload = function() {
  30. // Draw the image on the canvas
  31. originalImage = img;
  32. drawAndPrepImage(img);
  33. // Show the converter dialog
  34. const overlay = document.getElementById('image-converter');
  35. overlay.classList.remove('hidden');
  36. overlay.classList.add('visible');
  37. // Initialize the UI elements
  38. initializeUI();
  39. // Convert the image with default settings
  40. convertImage();
  41. };
  42. img.onerror = function() {
  43. logMessage(`Failed to load image: ${file.name}`, LOG_TYPE.ERROR);
  44. };
  45. // Load the image from the file
  46. img.src = URL.createObjectURL(file);
  47. }
  48. /**
  49. * Initialize UI elements for the image converter
  50. */
  51. function initializeUI() {
  52. // Set up event listeners for UI controls
  53. const epsilonSlider = document.getElementById('epsilon-slider');
  54. const epsilonValueDisplay = document.getElementById('epsilon-value-display');
  55. epsilonSlider.addEventListener('input', function() {
  56. epsilonValueDisplay.textContent = this.value;
  57. });
  58. // Set up event listeners for other controls
  59. //document.getElementById('epsilon-slider').addEventListener('change', convertImage);
  60. //document.getElementById('dot-number').addEventListener('change', convertImage);
  61. //document.getElementById('contour-mode').addEventListener('change', convertImage);
  62. //document.getElementById('is-loop').addEventListener('change', convertImage);
  63. //document.getElementById('no-shortcuts').addEventListener('change', convertImage);
  64. }
  65. /**
  66. * Save the converted pattern as a .thr file
  67. */
  68. async function saveConvertedPattern() {
  69. convertedCoordinates = document.getElementById('polar-coordinates-textarea').value;
  70. if (!convertedCoordinates) {
  71. logMessage('No converted coordinates to save.', LOG_TYPE.ERROR);
  72. return;
  73. }
  74. try {
  75. // Create a safe filename (replace spaces and special characters)
  76. const safeFileName = fileName.replace(/[^a-z0-9]/gi, '_').toLowerCase();
  77. const thrFileName = `${safeFileName}.thr`;
  78. // Create a Blob with the coordinates
  79. const blob = new Blob([convertedCoordinates], { type: 'text/plain' });
  80. // Create a FormData object
  81. const formData = new FormData();
  82. formData.append('file', new File([blob], thrFileName, { type: 'text/plain' }));
  83. // Show processing indicator
  84. const processingIndicator = document.getElementById('processing-status');
  85. const processingMessage = document.getElementById('processing-message');
  86. if (processingMessage) {
  87. processingMessage.textContent = `Saving pattern as ${thrFileName}...`;
  88. }
  89. processingIndicator.classList.add('visible');
  90. // Upload the file
  91. const response = await fetch('/upload_theta_rho', {
  92. method: 'POST',
  93. body: formData
  94. });
  95. const result = await response.json();
  96. if (result.success) {
  97. const fileInput = document.getElementById('upload_file');
  98. const finalFileName = 'custom_patterns/' + thrFileName;
  99. logMessage(`Image converted and saved as ${finalFileName}`, LOG_TYPE.SUCCESS);
  100. // Close the converter dialog
  101. closeImageConverter();
  102. // clear the file input
  103. fileInput.value = '';
  104. // Refresh the file list
  105. await loadThetaRhoFiles();
  106. // Select the newly created file
  107. const fileList = document.getElementById('theta_rho_files');
  108. const listItems = fileList.querySelectorAll('li');
  109. for (const item of listItems) {
  110. if (item.textContent === finalFileName) {
  111. selectFile(finalFileName, item);
  112. break;
  113. }
  114. }
  115. } else {
  116. logMessage(`Failed to save pattern: ${result.error || 'Unknown error'}`, LOG_TYPE.ERROR);
  117. }
  118. } catch (error) {
  119. logMessage(`Error saving pattern: ${error.message}`, LOG_TYPE.ERROR);
  120. } finally {
  121. // Hide processing indicator
  122. document.getElementById('processing-status').classList.remove('visible');
  123. }
  124. }
  125. /**
  126. * Clear a canvas
  127. * @param {string} canvasId - The ID of the canvas element to clear
  128. */
  129. function clearCanvas(canvasId) {
  130. const canvas = document.getElementById(canvasId);
  131. const ctx = canvas.getContext('2d');
  132. ctx.clearRect(0, 0, canvas.width, canvas.height);
  133. }
  134. /**
  135. * Close the image converter dialog
  136. */
  137. function closeImageConverter() {
  138. const overlay = document.getElementById('image-converter');
  139. overlay.classList.remove('visible');
  140. overlay.classList.add('hidden');
  141. // Clear the canvases
  142. clearCanvas('original-image');
  143. clearCanvas('edge-image');
  144. clearCanvas('dot-image');
  145. clearCanvas('connect-image');
  146. // Reset variables
  147. originalImage = null;
  148. fileName = '';
  149. convertedCoordinates = null;
  150. currentImageData = null;
  151. // Disable the save button
  152. //document.getElementById('save-pattern-button').disabled = true;
  153. }
  154. async function generateOpenAIImage(apiKey, prompt) {
  155. if (isGeneratingImage) {
  156. logMessage("Image is still generating - please don't press the button.", LOG_TYPE.INFO);
  157. } else {
  158. isGeneratingImage = true;
  159. clearCanvas('original-image');
  160. clearCanvas('edge-image');
  161. clearCanvas('dot-image');
  162. clearCanvas('connect-image');
  163. document.getElementById('gen-image-button').disabled = true;
  164. // Show processing indicator
  165. const processingIndicator = document.getElementById('processing-status');
  166. const processingMessage = document.getElementById('processing-message');
  167. if (processingMessage) {
  168. processingMessage.textContent = `Generating image...`;
  169. }
  170. processingIndicator.classList.add('visible');
  171. try {
  172. const fullPrompt = `Draw an image of the following: ${prompt}. Make the line black and the background white. The drawing should be a single line, don't add any additional details to the image.`;
  173. const response = await fetch('https://api.openai.com/v1/images/generations', {
  174. method: 'POST',
  175. headers: {
  176. 'Authorization': `Bearer ${apiKey}`,
  177. 'Content-Type': 'application/json'
  178. },
  179. body: JSON.stringify({
  180. model: 'dall-e-3',
  181. prompt: fullPrompt,
  182. size: '1024x1024',
  183. quality: 'standard',
  184. response_format: 'b64_json', // Specify base64 encoding
  185. n: 1
  186. })
  187. });
  188. const data = await response.json();
  189. //const imageUrl = data.data[0].url;
  190. if ('error' in data) {
  191. throw new Error(data.error.message);
  192. }
  193. const imageData = data.data[0].b64_json;
  194. //console.log("Image Data: ", imageData);
  195. const imgElement = new Image();
  196. imgElement.onload = function() {
  197. // Draw the image on the canvas
  198. originalImage = imgElement;
  199. drawAndPrepImage(imgElement);
  200. // Convert the image with default settings
  201. convertImage();
  202. };
  203. imgElement.src = `data:image/png;base64,${imageData}`;
  204. //console.log(`Image generated successfully`);
  205. logMessage('Image generated successfully', LOG_TYPE.SUCCESS);
  206. } catch (error) {
  207. //console.error('Image generation error:', error);
  208. logMessage('Image generation error: ' + error, LOG_TYPE.ERROR);
  209. }
  210. isGeneratingImage = false;
  211. document.getElementById('gen-image-button').disabled = false;
  212. document.getElementById('processing-status').classList.remove('visible');
  213. }
  214. }
  215. function regeneratePattern() {
  216. const generateButton = document.getElementById('generate-button');
  217. // Disable button & show existing loader
  218. generateButton.disabled = true;
  219. generateButton.classList.add('loading');
  220. // Wrap convertImage() in a Promise
  221. new Promise((resolve, reject) => {
  222. try {
  223. convertImage();
  224. setTimeout(resolve, 1000);
  225. } catch (error) {
  226. reject(error);
  227. }
  228. })
  229. .then(() => {
  230. logMessage("Pattern regenerated successfully.", LOG_TYPE.SUCCESS);
  231. })
  232. .catch(error => {
  233. logMessage("Error regenerating pattern: " + error.message, LOG_TYPE.ERROR);
  234. })
  235. .finally(() => {
  236. // Re-enable button & hide loader
  237. generateButton.disabled = false;
  238. generateButton.classList.remove('loading');
  239. });
  240. }
  241. // Override the uploadThetaRho function to handle image files
  242. const originalUploadThetaRho = window.uploadThetaRho;
  243. window.uploadThetaRho = async function() {
  244. const fileInput = document.getElementById('upload_file');
  245. const file = fileInput.files[0];
  246. if (!file) {
  247. logMessage('No file selected for upload.', LOG_TYPE.ERROR);
  248. return;
  249. }
  250. // Check if the file is an image
  251. if (file.type.startsWith('image/')) {
  252. // Handle image files with the converter
  253. openImageConverter(file);
  254. return;
  255. }
  256. // For non-image files, use the original function
  257. await originalUploadThetaRho();
  258. };
  259. // Remove existing event listener and add a new one
  260. document.getElementById('gen-image-button')?.addEventListener('click', function() {
  261. let apiKey = document.getElementById('api-key')?.value || '';
  262. const googlyEyes = document.getElementById('googly-eyes');
  263. const promptElement = document.getElementById('prompt');
  264. // Add null checks
  265. const promptValue = promptElement?.value || '';
  266. const googlyEyesChecked = googlyEyes?.checked || false;
  267. const prompt = promptValue + (googlyEyesChecked ? ' with disproportionately large googly eyes' : '');
  268. // Show the converter dialog
  269. const overlay = document.getElementById('image-converter');
  270. if (overlay) {
  271. overlay.classList.remove('hidden');
  272. // Initialize the UI elements
  273. initializeUI();
  274. generateOpenAIImage(apiKey, prompt);
  275. }
  276. });