ImageReader.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  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 .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. self._ui.showConfigUI()
  20. self._ui.waitForUIToClose()
  21. if self._ui.getCancelled():
  22. return MeshReader.PreReadResult.cancelled
  23. return MeshReader.PreReadResult.accepted
  24. def read(self, file_name):
  25. return self._generateSceneNode(file_name, self._ui.size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512)
  26. def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size):
  27. mesh = None
  28. scene_node = None
  29. scene_node = SceneNode()
  30. mesh = MeshData()
  31. scene_node.setMeshData(mesh)
  32. img = QImage(file_name)
  33. width = max(img.width(), 2)
  34. height = max(img.height(), 2)
  35. aspect = height / width
  36. if img.width() < 2 or img.height() < 2:
  37. img = img.scaled(width, height, Qt.IgnoreAspectRatio)
  38. base_height = max(base_height, 0)
  39. xz_size = max(xz_size, 1)
  40. scale_vector = Vector(xz_size, max(peak_height - base_height, -base_height), xz_size)
  41. if width > height:
  42. scale_vector.setZ(scale_vector.z * aspect)
  43. elif height > width:
  44. scale_vector.setX(scale_vector.x / aspect)
  45. if width > max_size or height > max_size:
  46. scale_factor = max_size / width
  47. if height > width:
  48. scale_factor = max_size / height
  49. width = int(max(round(width * scale_factor), 2))
  50. height = int(max(round(height * scale_factor), 2))
  51. img = img.scaled(width, height, Qt.IgnoreAspectRatio)
  52. width_minus_one = width - 1
  53. height_minus_one = height - 1
  54. Job.yieldThread()
  55. texel_width = 1.0 / (width_minus_one) * scale_vector.x
  56. texel_height = 1.0 / (height_minus_one) * scale_vector.z
  57. height_data = numpy.zeros((height, width), dtype=numpy.float32)
  58. for x in range(0, width):
  59. for y in range(0, height):
  60. qrgb = img.pixel(x, y)
  61. avg = float(qRed(qrgb) + qGreen(qrgb) + qBlue(qrgb)) / (3 * 255)
  62. height_data[y, x] = avg
  63. Job.yieldThread()
  64. for i in range(0, blur_iterations):
  65. copy = numpy.pad(height_data, ((1, 1), (1, 1)), mode='edge')
  66. height_data += copy[1:-1, 2:]
  67. height_data += copy[1:-1, :-2]
  68. height_data += copy[2:, 1:-1]
  69. height_data += copy[:-2, 1:-1]
  70. height_data += copy[2:, 2:]
  71. height_data += copy[:-2, 2:]
  72. height_data += copy[2:, :-2]
  73. height_data += copy[:-2, :-2]
  74. height_data /= 9
  75. Job.yieldThread()
  76. height_data *= scale_vector.y
  77. height_data += base_height
  78. heightmap_face_count = 2 * height_minus_one * width_minus_one
  79. total_face_count = heightmap_face_count + (width_minus_one * 2) * (height_minus_one * 2) + 2
  80. mesh.reserveFaceCount(total_face_count)
  81. # initialize to texel space vertex offsets.
  82. # 6 is for 6 vertices for each texel quad.
  83. heightmap_vertices = numpy.zeros((width_minus_one * height_minus_one, 6, 3), dtype=numpy.float32)
  84. heightmap_vertices = heightmap_vertices + numpy.array([[
  85. [0, base_height, 0],
  86. [0, base_height, texel_height],
  87. [texel_width, base_height, texel_height],
  88. [texel_width, base_height, texel_height],
  89. [texel_width, base_height, 0],
  90. [0, base_height, 0]
  91. ]], dtype=numpy.float32)
  92. offsetsz, offsetsx = numpy.mgrid[0:height_minus_one, 0:width-1]
  93. offsetsx = numpy.array(offsetsx, numpy.float32).reshape(-1, 1) * texel_width
  94. offsetsz = numpy.array(offsetsz, numpy.float32).reshape(-1, 1) * texel_height
  95. # offsets for each texel quad
  96. heightmap_vertex_offsets = numpy.concatenate([offsetsx, numpy.zeros((offsetsx.shape[0], offsetsx.shape[1]), dtype=numpy.float32), offsetsz], 1)
  97. heightmap_vertices += heightmap_vertex_offsets.repeat(6, 0).reshape(-1, 6, 3)
  98. # apply height data to y values
  99. heightmap_vertices[:, 0, 1] = heightmap_vertices[:, 5, 1] = height_data[:-1, :-1].reshape(-1)
  100. heightmap_vertices[:, 1, 1] = height_data[1:, :-1].reshape(-1)
  101. heightmap_vertices[:, 2, 1] = heightmap_vertices[:, 3, 1] = height_data[1:, 1:].reshape(-1)
  102. heightmap_vertices[:, 4, 1] = height_data[:-1, 1:].reshape(-1)
  103. heightmap_indices = numpy.array(numpy.mgrid[0:heightmap_face_count * 3], dtype=numpy.int32).reshape(-1, 3)
  104. mesh._vertices[0:(heightmap_vertices.size // 3), :] = heightmap_vertices.reshape(-1, 3)
  105. mesh._indices[0:(heightmap_indices.size // 3), :] = heightmap_indices
  106. mesh._vertex_count = heightmap_vertices.size // 3
  107. mesh._face_count = heightmap_indices.size // 3
  108. geo_width = width_minus_one * texel_width
  109. geo_height = height_minus_one * texel_height
  110. # bottom
  111. mesh.addFace(0, 0, 0, 0, 0, geo_height, geo_width, 0, geo_height)
  112. mesh.addFace(geo_width, 0, geo_height, geo_width, 0, 0, 0, 0, 0)
  113. # north and south walls
  114. for n in range(0, width_minus_one):
  115. x = n * texel_width
  116. nx = (n + 1) * texel_width
  117. hn0 = height_data[0, n]
  118. hn1 = height_data[0, n + 1]
  119. hs0 = height_data[height_minus_one, n]
  120. hs1 = height_data[height_minus_one, n + 1]
  121. mesh.addFace(x, 0, 0, nx, 0, 0, nx, hn1, 0)
  122. mesh.addFace(nx, hn1, 0, x, hn0, 0, x, 0, 0)
  123. mesh.addFace(x, 0, geo_height, nx, 0, geo_height, nx, hs1, geo_height)
  124. mesh.addFace(nx, hs1, geo_height, x, hs0, geo_height, x, 0, geo_height)
  125. # west and east walls
  126. for n in range(0, height_minus_one):
  127. y = n * texel_height
  128. ny = (n + 1) * texel_height
  129. hw0 = height_data[n, 0]
  130. hw1 = height_data[n + 1, 0]
  131. he0 = height_data[n, width_minus_one]
  132. he1 = height_data[n + 1, width_minus_one]
  133. mesh.addFace(0, 0, y, 0, 0, ny, 0, hw1, ny)
  134. mesh.addFace(0, hw1, ny, 0, hw0, y, 0, 0, y)
  135. mesh.addFace(geo_width, 0, y, geo_width, 0, ny, geo_width, he1, ny)
  136. mesh.addFace(geo_width, he1, ny, geo_width, he0, y, geo_width, 0, y)
  137. mesh.calculateNormals(fast=True)
  138. return scene_node