ImageReader.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. # Copyright (c) 2015 Ultimaker B.V.
  2. # Cura is released under the terms of the AGPLv3 or higher.
  3. import os
  4. import numpy
  5. from PyQt5.QtGui import QImage, qRed, qGreen, qBlue
  6. from PyQt5.QtCore import Qt
  7. from UM.Mesh.MeshReader import MeshReader
  8. from UM.Mesh.MeshData import MeshData
  9. from UM.Scene.SceneNode import SceneNode
  10. from UM.Math.Vector import Vector
  11. from UM.Job import Job
  12. from UM.Logger import Logger
  13. from .ImageReaderUI import ImageReaderUI
  14. class ImageReader(MeshReader):
  15. def __init__(self):
  16. super(ImageReader, self).__init__()
  17. self._supported_extensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png"]
  18. self._ui = ImageReaderUI(self)
  19. def preRead(self, file_name):
  20. img = QImage(file_name)
  21. if img.isNull():
  22. Logger.log("e", "Image is corrupt.")
  23. return MeshReader.PreReadResult.failed
  24. width = img.width()
  25. depth = img.height()
  26. largest = max(width, depth)
  27. width = width / largest * self._ui.default_width
  28. depth = depth / largest * self._ui.default_depth
  29. self._ui.setWidthAndDepth(width, depth)
  30. self._ui.showConfigUI()
  31. self._ui.waitForUIToClose()
  32. if self._ui.getCancelled():
  33. return MeshReader.PreReadResult.cancelled
  34. return MeshReader.PreReadResult.accepted
  35. def read(self, file_name):
  36. size = max(self._ui.getWidth(), self._ui.getDepth())
  37. return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.image_color_invert)
  38. def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, image_color_invert):
  39. mesh = None
  40. scene_node = None
  41. scene_node = SceneNode()
  42. mesh = MeshData()
  43. scene_node.setMeshData(mesh)
  44. img = QImage(file_name)
  45. if img.isNull():
  46. Logger.log("e", "Image is corrupt.")
  47. return None
  48. width = max(img.width(), 2)
  49. height = max(img.height(), 2)
  50. aspect = height / width
  51. if img.width() < 2 or img.height() < 2:
  52. img = img.scaled(width, height, Qt.IgnoreAspectRatio)
  53. base_height = max(base_height, 0)
  54. peak_height = max(peak_height, -base_height)
  55. xz_size = max(xz_size, 1)
  56. scale_vector = Vector(xz_size, peak_height, xz_size)
  57. if width > height:
  58. scale_vector.setZ(scale_vector.z * aspect)
  59. elif height > width:
  60. scale_vector.setX(scale_vector.x / aspect)
  61. if width > max_size or height > max_size:
  62. scale_factor = max_size / width
  63. if height > width:
  64. scale_factor = max_size / height
  65. width = int(max(round(width * scale_factor), 2))
  66. height = int(max(round(height * scale_factor), 2))
  67. img = img.scaled(width, height, Qt.IgnoreAspectRatio)
  68. width_minus_one = width - 1
  69. height_minus_one = height - 1
  70. Job.yieldThread()
  71. texel_width = 1.0 / (width_minus_one) * scale_vector.x
  72. texel_height = 1.0 / (height_minus_one) * scale_vector.z
  73. height_data = numpy.zeros((height, width), dtype=numpy.float32)
  74. for x in range(0, width):
  75. for y in range(0, height):
  76. qrgb = img.pixel(x, y)
  77. avg = float(qRed(qrgb) + qGreen(qrgb) + qBlue(qrgb)) / (3 * 255)
  78. height_data[y, x] = avg
  79. Job.yieldThread()
  80. if image_color_invert:
  81. height_data = 1 - height_data
  82. for i in range(0, blur_iterations):
  83. copy = numpy.pad(height_data, ((1, 1), (1, 1)), mode= "edge")
  84. height_data += copy[1:-1, 2:]
  85. height_data += copy[1:-1, :-2]
  86. height_data += copy[2:, 1:-1]
  87. height_data += copy[:-2, 1:-1]
  88. height_data += copy[2:, 2:]
  89. height_data += copy[:-2, 2:]
  90. height_data += copy[2:, :-2]
  91. height_data += copy[:-2, :-2]
  92. height_data /= 9
  93. Job.yieldThread()
  94. height_data *= scale_vector.y
  95. height_data += base_height
  96. heightmap_face_count = 2 * height_minus_one * width_minus_one
  97. total_face_count = heightmap_face_count + (width_minus_one * 2) * (height_minus_one * 2) + 2
  98. mesh.reserveFaceCount(total_face_count)
  99. # initialize to texel space vertex offsets.
  100. # 6 is for 6 vertices for each texel quad.
  101. heightmap_vertices = numpy.zeros((width_minus_one * height_minus_one, 6, 3), dtype=numpy.float32)
  102. heightmap_vertices = heightmap_vertices + numpy.array([[
  103. [0, base_height, 0],
  104. [0, base_height, texel_height],
  105. [texel_width, base_height, texel_height],
  106. [texel_width, base_height, texel_height],
  107. [texel_width, base_height, 0],
  108. [0, base_height, 0]
  109. ]], dtype=numpy.float32)
  110. offsetsz, offsetsx = numpy.mgrid[0:height_minus_one, 0:width-1]
  111. offsetsx = numpy.array(offsetsx, numpy.float32).reshape(-1, 1) * texel_width
  112. offsetsz = numpy.array(offsetsz, numpy.float32).reshape(-1, 1) * texel_height
  113. # offsets for each texel quad
  114. heightmap_vertex_offsets = numpy.concatenate([offsetsx, numpy.zeros((offsetsx.shape[0], offsetsx.shape[1]), dtype=numpy.float32), offsetsz], 1)
  115. heightmap_vertices += heightmap_vertex_offsets.repeat(6, 0).reshape(-1, 6, 3)
  116. # apply height data to y values
  117. heightmap_vertices[:, 0, 1] = heightmap_vertices[:, 5, 1] = height_data[:-1, :-1].reshape(-1)
  118. heightmap_vertices[:, 1, 1] = height_data[1:, :-1].reshape(-1)
  119. heightmap_vertices[:, 2, 1] = heightmap_vertices[:, 3, 1] = height_data[1:, 1:].reshape(-1)
  120. heightmap_vertices[:, 4, 1] = height_data[:-1, 1:].reshape(-1)
  121. heightmap_indices = numpy.array(numpy.mgrid[0:heightmap_face_count * 3], dtype=numpy.int32).reshape(-1, 3)
  122. mesh._vertices[0:(heightmap_vertices.size // 3), :] = heightmap_vertices.reshape(-1, 3)
  123. mesh._indices[0:(heightmap_indices.size // 3), :] = heightmap_indices
  124. mesh._vertex_count = heightmap_vertices.size // 3
  125. mesh._face_count = heightmap_indices.size // 3
  126. geo_width = width_minus_one * texel_width
  127. geo_height = height_minus_one * texel_height
  128. # bottom
  129. mesh.addFace(0, 0, 0, 0, 0, geo_height, geo_width, 0, geo_height)
  130. mesh.addFace(geo_width, 0, geo_height, geo_width, 0, 0, 0, 0, 0)
  131. # north and south walls
  132. for n in range(0, width_minus_one):
  133. x = n * texel_width
  134. nx = (n + 1) * texel_width
  135. hn0 = height_data[0, n]
  136. hn1 = height_data[0, n + 1]
  137. hs0 = height_data[height_minus_one, n]
  138. hs1 = height_data[height_minus_one, n + 1]
  139. mesh.addFace(x, 0, 0, nx, 0, 0, nx, hn1, 0)
  140. mesh.addFace(nx, hn1, 0, x, hn0, 0, x, 0, 0)
  141. mesh.addFace(x, 0, geo_height, nx, 0, geo_height, nx, hs1, geo_height)
  142. mesh.addFace(nx, hs1, geo_height, x, hs0, geo_height, x, 0, geo_height)
  143. # west and east walls
  144. for n in range(0, height_minus_one):
  145. y = n * texel_height
  146. ny = (n + 1) * texel_height
  147. hw0 = height_data[n, 0]
  148. hw1 = height_data[n + 1, 0]
  149. he0 = height_data[n, width_minus_one]
  150. he1 = height_data[n + 1, width_minus_one]
  151. mesh.addFace(0, 0, y, 0, 0, ny, 0, hw1, ny)
  152. mesh.addFace(0, hw1, ny, 0, hw0, y, 0, 0, y)
  153. mesh.addFace(geo_width, 0, y, geo_width, 0, ny, geo_width, he1, ny)
  154. mesh.addFace(geo_width, he1, ny, geo_width, he0, y, geo_width, 0, y)
  155. mesh.calculateNormals(fast=True)
  156. return scene_node