ImageReader.py 7.7 KB

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