api-native-image-spec.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. 'use strict'
  2. const assert = require('assert')
  3. const {nativeImage} = require('electron')
  4. const path = require('path')
  5. describe('nativeImage module', () => {
  6. describe('createEmpty()', () => {
  7. it('returns an empty image', () => {
  8. const empty = nativeImage.createEmpty()
  9. assert.equal(empty.isEmpty(), true)
  10. assert.equal(empty.getAspectRatio(), 1)
  11. assert.equal(empty.toDataURL(), 'data:image/png;base64,')
  12. assert.equal(empty.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,')
  13. assert.deepEqual(empty.getSize(), {width: 0, height: 0})
  14. assert.deepEqual(empty.getBitmap(), [])
  15. assert.deepEqual(empty.getBitmap({scaleFactor: 2.0}), [])
  16. assert.deepEqual(empty.toBitmap(), [])
  17. assert.deepEqual(empty.toBitmap({scaleFactor: 2.0}), [])
  18. assert.deepEqual(empty.toJPEG(100), [])
  19. assert.deepEqual(empty.toPNG(), [])
  20. assert.deepEqual(empty.toPNG({scaleFactor: 2.0}), [])
  21. if (process.platform === 'darwin') {
  22. assert.deepEqual(empty.getNativeHandle(), [])
  23. }
  24. })
  25. })
  26. describe('createFromBuffer(buffer, scaleFactor)', () => {
  27. it('returns an empty image when the buffer is empty', () => {
  28. assert(nativeImage.createFromBuffer(Buffer.from([])).isEmpty())
  29. })
  30. it('returns an image created from the given buffer', () => {
  31. const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png'))
  32. const imageB = nativeImage.createFromBuffer(imageA.toPNG())
  33. assert.deepEqual(imageB.getSize(), {width: 538, height: 190})
  34. assert(imageA.toBitmap().equals(imageB.toBitmap()))
  35. const imageC = nativeImage.createFromBuffer(imageA.toJPEG(100))
  36. assert.deepEqual(imageC.getSize(), {width: 538, height: 190})
  37. const imageD = nativeImage.createFromBuffer(imageA.toBitmap(),
  38. {width: 538, height: 190})
  39. assert.deepEqual(imageD.getSize(), {width: 538, height: 190})
  40. const imageE = nativeImage.createFromBuffer(imageA.toBitmap(),
  41. {width: 100, height: 200})
  42. assert.deepEqual(imageE.getSize(), {width: 100, height: 200})
  43. const imageF = nativeImage.createFromBuffer(imageA.toBitmap())
  44. assert(imageF.isEmpty())
  45. const imageG = nativeImage.createFromBuffer(imageA.toPNG(),
  46. {width: 100, height: 200})
  47. assert.deepEqual(imageG.getSize(), {width: 538, height: 190})
  48. const imageH = nativeImage.createFromBuffer(imageA.toJPEG(100),
  49. {width: 100, height: 200})
  50. assert.deepEqual(imageH.getSize(), {width: 538, height: 190})
  51. const imageI = nativeImage.createFromBuffer(imageA.toBitmap(),
  52. {width: 538, height: 190, scaleFactor: 2.0})
  53. assert.deepEqual(imageI.getSize(), {width: 269, height: 95})
  54. const imageJ = nativeImage.createFromBuffer(imageA.toPNG(), 2.0)
  55. assert.deepEqual(imageJ.getSize(), {width: 269, height: 95})
  56. })
  57. })
  58. describe('createFromDataURL(dataURL)', () => {
  59. it('returns an empty image when the dataURL is empty', () => {
  60. assert(nativeImage.createFromDataURL('').isEmpty())
  61. })
  62. it('returns an image created from the given buffer', () => {
  63. const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png'))
  64. const imageB = nativeImage.createFromDataURL(imageA.toDataURL())
  65. assert.deepEqual(imageB.getSize(), {width: 538, height: 190})
  66. assert(imageA.toBitmap().equals(imageB.toBitmap()))
  67. })
  68. })
  69. describe('toDataURL()', () => {
  70. it('returns a PNG data URL', () => {
  71. const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '1x1.png'))
  72. assert.equal(imageA.toDataURL(), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=')
  73. assert.equal(imageA.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=')
  74. })
  75. it('returns a data URL at 1x scale factor by default', () => {
  76. const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png'))
  77. const imageB = nativeImage.createFromBuffer(imageA.toPNG(), {
  78. width: imageA.getSize().width,
  79. height: imageA.getSize().height,
  80. scaleFactor: 2.0
  81. })
  82. assert.deepEqual(imageB.getSize(), {width: 269, height: 95})
  83. const imageC = nativeImage.createFromDataURL(imageB.toDataURL())
  84. assert.deepEqual(imageC.getSize(), {width: 538, height: 190})
  85. assert(imageB.toBitmap().equals(imageC.toBitmap()))
  86. })
  87. it('supports a scale factor', () => {
  88. const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png'))
  89. const imageB = nativeImage.createFromDataURL(imageA.toDataURL({scaleFactor: 1.0}))
  90. assert.deepEqual(imageB.getSize(), {width: 538, height: 190})
  91. const imageC = nativeImage.createFromDataURL(imageA.toDataURL({scaleFactor: 2.0}))
  92. assert.deepEqual(imageC.getSize(), {width: 538, height: 190})
  93. })
  94. })
  95. describe('toPNG()', () => {
  96. it('returns a buffer at 1x scale factor by default', () => {
  97. const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png'))
  98. const imageB = nativeImage.createFromBuffer(imageA.toPNG(), {
  99. width: imageA.getSize().width,
  100. height: imageA.getSize().height,
  101. scaleFactor: 2.0
  102. })
  103. assert.deepEqual(imageB.getSize(), {width: 269, height: 95})
  104. const imageC = nativeImage.createFromBuffer(imageB.toPNG())
  105. assert.deepEqual(imageC.getSize(), {width: 538, height: 190})
  106. assert(imageB.toBitmap().equals(imageC.toBitmap()))
  107. })
  108. it('supports a scale factor', () => {
  109. const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png'))
  110. const imageB = nativeImage.createFromBuffer(imageA.toPNG({scaleFactor: 1.0}))
  111. assert.deepEqual(imageB.getSize(), {width: 538, height: 190})
  112. const imageC = nativeImage.createFromBuffer(imageA.toPNG({scaleFactor: 2.0}), {scaleFactor: 2.0})
  113. assert.deepEqual(imageC.getSize(), {width: 269, height: 95})
  114. })
  115. })
  116. describe('createFromPath(path)', () => {
  117. it('returns an empty image for invalid paths', () => {
  118. assert(nativeImage.createFromPath('').isEmpty())
  119. assert(nativeImage.createFromPath('does-not-exist.png').isEmpty())
  120. assert(nativeImage.createFromPath('does-not-exist.ico').isEmpty())
  121. assert(nativeImage.createFromPath(__dirname).isEmpty())
  122. assert(nativeImage.createFromPath(__filename).isEmpty())
  123. })
  124. it('loads images from paths relative to the current working directory', () => {
  125. const imagePath = `.${path.sep}${path.join('spec', 'fixtures', 'assets', 'logo.png')}`
  126. const image = nativeImage.createFromPath(imagePath)
  127. assert(!image.isEmpty())
  128. assert.deepEqual(image.getSize(), {width: 538, height: 190})
  129. })
  130. it('loads images from paths with `.` segments', () => {
  131. const imagePath = `${path.join(__dirname, 'fixtures')}${path.sep}.${path.sep}${path.join('assets', 'logo.png')}`
  132. const image = nativeImage.createFromPath(imagePath)
  133. assert(!image.isEmpty())
  134. assert.deepEqual(image.getSize(), {width: 538, height: 190})
  135. })
  136. it('loads images from paths with `..` segments', () => {
  137. const imagePath = `${path.join(__dirname, 'fixtures', 'api')}${path.sep}..${path.sep}${path.join('assets', 'logo.png')}`
  138. const image = nativeImage.createFromPath(imagePath)
  139. assert(!image.isEmpty())
  140. assert.deepEqual(image.getSize(), {width: 538, height: 190})
  141. })
  142. it('Gets an NSImage pointer on macOS', () => {
  143. if (process.platform !== 'darwin') return
  144. const imagePath = `${path.join(__dirname, 'fixtures', 'api')}${path.sep}..${path.sep}${path.join('assets', 'logo.png')}`
  145. const image = nativeImage.createFromPath(imagePath)
  146. const nsimage = image.getNativeHandle()
  147. assert.equal(nsimage.length, 8)
  148. // If all bytes are null, that's Bad
  149. assert.equal(nsimage.reduce((acc, x) => acc || (x !== 0), false), true)
  150. })
  151. it('loads images from .ico files on Windows', () => {
  152. if (process.platform !== 'win32') return
  153. const imagePath = path.join(__dirname, 'fixtures', 'assets', 'icon.ico')
  154. const image = nativeImage.createFromPath(imagePath)
  155. assert(!image.isEmpty())
  156. assert.deepEqual(image.getSize(), {width: 256, height: 256})
  157. })
  158. })
  159. describe('resize(options)', () => {
  160. it('returns a resized image', () => {
  161. const image = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png'))
  162. assert.deepEqual(image.resize({}).getSize(), {width: 538, height: 190})
  163. assert.deepEqual(image.resize({width: 269}).getSize(), {width: 269, height: 95})
  164. assert.deepEqual(image.resize({width: 600}).getSize(), {width: 600, height: 212})
  165. assert.deepEqual(image.resize({height: 95}).getSize(), {width: 269, height: 95})
  166. assert.deepEqual(image.resize({height: 200}).getSize(), {width: 566, height: 200})
  167. assert.deepEqual(image.resize({width: 80, height: 65}).getSize(), {width: 80, height: 65})
  168. assert.deepEqual(image.resize({width: 600, height: 200}).getSize(), {width: 600, height: 200})
  169. assert.deepEqual(image.resize({width: 0, height: 0}).getSize(), {width: 0, height: 0})
  170. assert.deepEqual(image.resize({width: -1, height: -1}).getSize(), {width: 0, height: 0})
  171. })
  172. it('returns an empty image when called on an empty image', () => {
  173. assert(nativeImage.createEmpty().resize({width: 1, height: 1}).isEmpty())
  174. assert(nativeImage.createEmpty().resize({width: 0, height: 0}).isEmpty())
  175. })
  176. it('supports a quality option', () => {
  177. const image = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png'))
  178. const good = image.resize({width: 100, height: 100, quality: 'good'})
  179. const better = image.resize({width: 100, height: 100, quality: 'better'})
  180. const best = image.resize({width: 100, height: 100, quality: 'best'})
  181. assert(good.toPNG().length <= better.toPNG().length)
  182. assert(better.toPNG().length < best.toPNG().length)
  183. })
  184. })
  185. describe('crop(bounds)', () => {
  186. it('returns an empty image when called on an empty image', () => {
  187. assert(nativeImage.createEmpty().crop({width: 1, height: 2, x: 0, y: 0}).isEmpty())
  188. assert(nativeImage.createEmpty().crop({width: 0, height: 0, x: 0, y: 0}).isEmpty())
  189. })
  190. it('returns an empty image when the bounds are invalid', () => {
  191. const image = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png'))
  192. assert(image.crop({width: 0, height: 0, x: 0, y: 0}).isEmpty())
  193. assert(image.crop({width: -1, height: 10, x: 0, y: 0}).isEmpty())
  194. assert(image.crop({width: 10, height: -35, x: 0, y: 0}).isEmpty())
  195. assert(image.crop({width: 100, height: 100, x: 1000, y: 1000}).isEmpty())
  196. })
  197. it('returns a cropped image', () => {
  198. const image = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png'))
  199. const cropA = image.crop({width: 25, height: 64, x: 0, y: 0})
  200. const cropB = image.crop({width: 25, height: 64, x: 30, y: 40})
  201. assert.deepEqual(cropA.getSize(), {width: 25, height: 64})
  202. assert.deepEqual(cropB.getSize(), {width: 25, height: 64})
  203. assert(!cropA.toPNG().equals(cropB.toPNG()))
  204. })
  205. })
  206. describe('getAspectRatio()', () => {
  207. it('returns the aspect ratio of the image', () => {
  208. assert.equal(nativeImage.createEmpty().getAspectRatio(), 1.0)
  209. assert.equal(nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')).getAspectRatio(), 2.8315789699554443)
  210. })
  211. })
  212. describe('addRepresentation()', () => {
  213. it('supports adding a buffer representation for a scale factor', () => {
  214. const image = nativeImage.createEmpty()
  215. image.addRepresentation({
  216. scaleFactor: 1.0,
  217. buffer: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '1x1.png')).toPNG()
  218. })
  219. image.addRepresentation({
  220. scaleFactor: 2.0,
  221. buffer: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '2x2.jpg')).toPNG()
  222. })
  223. image.addRepresentation({
  224. scaleFactor: 3.0,
  225. buffer: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '3x3.png')).toPNG()
  226. })
  227. image.addRepresentation({
  228. scaleFactor: 4.0,
  229. buffer: 'invalid'
  230. })
  231. assert.equal(image.isEmpty(), false)
  232. assert.deepEqual(image.getSize(), {width: 1, height: 1})
  233. assert.equal(image.toDataURL({scaleFactor: 1.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=')
  234. assert.equal(image.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFUlEQVQYlWP8////fwYGBgYmBigAAD34BABBrq9BAAAAAElFTkSuQmCC')
  235. assert.equal(image.toDataURL({scaleFactor: 3.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAADElEQVQYlWNgIAoAAAAnAAGZWEMnAAAAAElFTkSuQmCC')
  236. assert.equal(image.toDataURL({scaleFactor: 4.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAADElEQVQYlWNgIAoAAAAnAAGZWEMnAAAAAElFTkSuQmCC')
  237. })
  238. it('supports adding a data URL representation for a scale factor', () => {
  239. const image = nativeImage.createEmpty()
  240. image.addRepresentation({
  241. scaleFactor: 1.0,
  242. dataURL: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '1x1.png')).toDataURL()
  243. })
  244. image.addRepresentation({
  245. scaleFactor: 2.0,
  246. dataURL: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '2x2.jpg')).toDataURL()
  247. })
  248. image.addRepresentation({
  249. scaleFactor: 3.0,
  250. dataURL: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '3x3.png')).toDataURL()
  251. })
  252. image.addRepresentation({
  253. scaleFactor: 4.0,
  254. dataURL: 'invalid'
  255. })
  256. assert.equal(image.isEmpty(), false)
  257. assert.deepEqual(image.getSize(), {width: 1, height: 1})
  258. assert.equal(image.toDataURL({scaleFactor: 1.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=')
  259. assert.equal(image.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFUlEQVQYlWP8////fwYGBgYmBigAAD34BABBrq9BAAAAAElFTkSuQmCC')
  260. assert.equal(image.toDataURL({scaleFactor: 3.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAADElEQVQYlWNgIAoAAAAnAAGZWEMnAAAAAElFTkSuQmCC')
  261. assert.equal(image.toDataURL({scaleFactor: 4.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAADElEQVQYlWNgIAoAAAAnAAGZWEMnAAAAAElFTkSuQmCC')
  262. })
  263. it('supports adding a representation to an existing image', () => {
  264. const image = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '1x1.png'))
  265. image.addRepresentation({
  266. scaleFactor: 2.0,
  267. dataURL: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '2x2.jpg')).toDataURL()
  268. })
  269. image.addRepresentation({
  270. scaleFactor: 2.0,
  271. dataURL: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '3x3.png')).toDataURL()
  272. })
  273. assert.equal(image.toDataURL({scaleFactor: 1.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=')
  274. assert.equal(image.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFUlEQVQYlWP8////fwYGBgYmBigAAD34BABBrq9BAAAAAElFTkSuQmCC')
  275. })
  276. })
  277. })