Uploadable.coffee 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. class Uploadable extends Class
  2. constructor: (@handleSave) ->
  3. @node = null
  4. @resize_width = 50
  5. @resize_height = 50
  6. @preverse_ratio = true # Keep image originial ratio
  7. @try_png = false # Try to convert to png
  8. @png_limit = 2200 # Use png instead of jpg is smaller than this size
  9. @image_preview = new ImagePreview()
  10. @pixel_chars = @image_preview.pixel_chars
  11. @
  12. storeNode: (node) =>
  13. @node = node
  14. scaleHalf: (image) ->
  15. canvas = document.createElement("canvas")
  16. canvas.width = image.width / 2
  17. canvas.height = image.height / 2
  18. ctx = canvas.getContext("2d")
  19. ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
  20. return canvas
  21. resizeImage: (file, width, height, cb) =>
  22. image = new Image()
  23. image.onload = =>
  24. @log "Resize image loaded"
  25. canvas = document.createElement("canvas")
  26. if @preverse_ratio
  27. [canvas.width, canvas.height] = @image_preview.calcSize(image.width, image.height, width, height)
  28. else
  29. canvas.width = width
  30. canvas.height = height
  31. ctx = canvas.getContext("2d")
  32. ctx.fillStyle = "#FFF"
  33. ctx.fillRect(0, 0, canvas.width, canvas.height)
  34. while image.width > width * 2
  35. image = @scaleHalf(image)
  36. ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
  37. # Try to optimize to png
  38. if @try_png
  39. quant = new RgbQuant({colors: 128, method: 1})
  40. quant.sample(canvas)
  41. quant.palette(true)
  42. canvas_quant = drawPixels(quant.reduce(canvas), width)
  43. optimizer = new CanvasTool.PngEncoder(canvas_quant, { bitDepth: 8, colourType: CanvasTool.PngEncoder.ColourType.TRUECOLOR })
  44. image_base64uri = "data:image/png;base64," + btoa(optimizer.convert())
  45. if image_base64uri.length > @png_limit
  46. # Too large, convert to jpg
  47. @log "PNG too large (#{image_base64uri.length} bytes), convert to jpg instead"
  48. image_base64uri = canvas.toDataURL("image/jpeg", 0.8)
  49. else
  50. image_base64uri = canvas.toDataURL("image/jpeg", 0.8)
  51. @log "Size: #{image_base64uri.length} bytes"
  52. cb image_base64uri, canvas.width, canvas.height
  53. image.onerror = (e) =>
  54. @log "Image upload error", e
  55. Page.cmd "wrapperNotification", ["error", "Invalid image, only jpg format supported"]
  56. cb null
  57. if file.name
  58. image.src = URL.createObjectURL(file)
  59. else
  60. image.src = file
  61. handleUploadClick: (e) =>
  62. @log "handleUploadClick", e
  63. script = document.createElement("script")
  64. script.src = "js-external/pngencoder.js"
  65. document.head.appendChild(script)
  66. input = document.createElement('input')
  67. document.body.appendChild(input)
  68. input.type = "file"
  69. input.style.visibility = "hidden"
  70. input.onchange = (e) =>
  71. @log "Uploaded"
  72. @resizeImage input.files[0], @resize_width, @resize_height, (image_base64uri, width, height) =>
  73. @log "Resized", width, height
  74. if image_base64uri
  75. @handleSave(image_base64uri, width, height)
  76. input.remove()
  77. input.click()
  78. return false
  79. render: (body) =>
  80. h("div.uploadable",
  81. h("a.icon.icon-upload", {href: "#Upload", onclick: @handleUploadClick})
  82. body()
  83. )
  84. getPixelData: (data) =>
  85. color_db = {}
  86. colors = []
  87. colors_next_id = 0
  88. pixels = []
  89. for i in [0..data.length-1] by 4
  90. r = data[i]
  91. g = data[i+1]
  92. b = data[i+2]
  93. #r = r - (r % 64)
  94. #g = g - (g % 64)
  95. #b = b - (b % 64)
  96. r = Math.round(r/17)
  97. g = Math.round(g/17)
  98. b = Math.round(b/17)
  99. hex = Number(0x1000 + r*0x100 + g*0x10 + b).toString(16).substring(1)
  100. if i == 0
  101. @log r, g, b, data[i+3], hex
  102. if !color_db[hex]
  103. color_db[hex] = @pixel_chars[colors_next_id]
  104. colors.push(hex)
  105. colors_next_id += 1
  106. pixels.push(color_db[hex])
  107. return [colors, pixels]
  108. getPreviewData: (image_base64uri, target_width, target_height, cb) ->
  109. image = new Image()
  110. image.src = image_base64uri
  111. image.onload = =>
  112. image_width = image.width
  113. image_height = image.height
  114. canvas = document.createElement("canvas")
  115. [canvas.width, canvas.height] = @image_preview.calcSize(image.width, image.height, target_width, target_height)
  116. ctx = canvas.getContext("2d")
  117. ctx.fillStyle = "#FFF"
  118. ctx.fillRect(0, 0, canvas.width, canvas.height)
  119. while image.width > target_width * 2
  120. image = @scaleHalf(image)
  121. ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
  122. quant = new RgbQuant({colors: 16, method: 1})
  123. quant.sample(canvas)
  124. quant.palette(true)
  125. canvas = drawPixels(quant.reduce(canvas), canvas.width)
  126. ctx = canvas.getContext("2d")
  127. image_data = ctx.getImageData(0, 0, canvas.width, canvas.height)
  128. pixeldata = @getPixelData(image_data.data)
  129. back = [image_width, image_height, pixeldata[0].join(""), pixeldata[1].join("")].join(",")
  130. @log "Previewdata size:", back.length
  131. cb back
  132. window.Uploadable = Uploadable