Merge branch 'master' into blender2.8
[blender-addons.git] / uv_bake_texture_to_vcols.py
blob0dc5e5496958204be9b22677eb876d268c8f69d4
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 # <pep8 compliant>
21 """
22 Bake UV-Texture to Vertex Colors Addon
24 Contact: p_boelens@msn.com
25 Information: https://developer.blender.org/T28211
27 Contributor(s): Patrick Boelens, CoDEmanX.
29 All rights reserved.
30 """
32 bl_info = {
33 "name": "Bake UV-Texture to Vertex Colors",
34 "description": "Bakes the colors of the active UV Texture "
35 "to a Vertex Color layer.",
36 "author": "Patrick Boelens, CoDEmanX",
37 "version": (0, 6),
38 "blender": (2, 63, 0),
39 "location": "3D View > Vertex Paint > Toolshelf > Bake",
40 "warning": "Requires image texture, generated textures aren't supported.",
41 "wiki_url": "http://wiki.blender.org/index.php?title=Extensions:2.6/Py/"
42 "Scripts/UV/Bake_Texture_to_Vertex_Colors",
43 "category": "UV",
46 import bpy
47 from bpy.props import BoolProperty, EnumProperty, FloatVectorProperty
48 from math import fabs
49 from colorsys import rgb_to_hsv, hsv_to_rgb
51 class UV_OT_bake_texture_to_vcols(bpy.types.Operator):
52 bl_idname = "uv.bake_texture_to_vcols"
53 bl_label = "Bake UV-Texture to Vertex Colors"
54 bl_description = "Bake active UV-Texture to new Vertex Color layer "\
55 "(requires image texture)"
56 bl_options = {'REGISTER', 'UNDO'}
58 replace_active_layer = BoolProperty(
59 name="Replace layer",
60 description="Overwrite active Vertex Color layer",
61 default=True)
63 mappingModes = [
64 ("CLIP", "Clip", "Don't affect vertices who's UV-coordinates are out of bounds."),
65 ("REPEAT", "Repeat", "Tile the image so that each vertex is accounted for."),
66 ("EXTEND", "Extend", "Extends the edges of the image to the UV-coordinates.")]
68 mappingMode = EnumProperty(
69 items=mappingModes,
70 default="CLIP",
71 name="Mapping",
72 description="The mode to use for baking vertices who's UV-coordinates are out of bounds")
74 blendingModes = [("MIX", "Mix", ""),
75 ("ADD", "Add", ""),
76 ("SUBTRACT", "Subtract", ""),
77 ("MULTIPLY", "Multiply", ""),
78 ("SCREEN", "Screen", ""),
79 ("OVERLAY", "Overlay", ""),
80 ("DIFFERENCE", "Difference", ""),
81 ("DIVIDE", "Divide", ""),
82 ("DARKEN", "Darken", ""),
83 ("LIGHTEN", "Lighten", ""),
84 ("HUE", "Hue", ""),
85 ("SATURATION", "Saturation", ""),
86 ("VALUE", "Value", ""),
87 ("COLOR", "Color", ""),
88 ("SOFT_LIGHT", "Soft Light", ""),
89 ("LINEAR_LIGHT", "Linear Light", "")
92 blendingMode = EnumProperty(
93 items=blendingModes,
94 default="MULTIPLY",
95 name="Blend Type",
96 description="The blending mode to use when baking")
98 mirror_x = BoolProperty(name="Mirror X", description="Mirror the image on the X-axis")
99 mirror_y = BoolProperty(name="Mirror Y", description="Mirror the image on the Y-axis")
101 @classmethod
102 def poll(self, context):
103 return (context.object and
104 context.object.type == 'MESH' and
105 context.mode != 'EDIT_MESH' and
106 context.object.data.uv_layers.active and
107 context.object.data.uv_textures.active)
109 def execute(self, context):
110 obdata = context.object.data
112 if self.replace_active_layer and obdata.vertex_colors.active:
113 vertex_colors = obdata.vertex_colors.active
114 else:
115 vertex_colors = obdata.vertex_colors.new(name="Baked UV texture")
117 if not vertex_colors:
119 # Can't add more than 17 VCol layers
120 self.report({'ERROR'},
121 "Couldn't add another Vertex Color layer,\n"
122 "Please remove an existing layer or replace active.")
124 return {'CANCELLED'}
126 obdata.vertex_colors.active = vertex_colors
128 uv_images = {}
129 for uv_tex in obdata.uv_textures.active.data:
130 if (uv_tex.image and
131 uv_tex.image.name not in uv_images and
132 uv_tex.image.pixels):
134 uv_images[uv_tex.image.name] = (
135 uv_tex.image.size[0],
136 uv_tex.image.size[1],
137 uv_tex.image.pixels[:]
138 # Accessing pixels directly is far too slow.
139 # Copied to new array for massive performance-gain.
142 for p in obdata.polygons:
143 img = obdata.uv_textures.active.data[p.index].image
144 if not img:
145 continue
147 image_size_x, image_size_y, uv_pixels = uv_images[img.name]
149 for loop in p.loop_indices:
151 co = obdata.uv_layers.active.data[loop].uv
152 x_co = round(co[0] * (image_size_x - 1))
153 y_co = round(co[1] * (image_size_y - 1))
155 if x_co < 0 or x_co >= image_size_x or y_co < 0 or y_co >= image_size_y:
156 if self.mappingMode == 'CLIP':
157 continue
159 elif self.mappingMode == 'REPEAT':
160 x_co %= image_size_x
161 y_co %= image_size_y
163 elif self.mappingMode == 'EXTEND':
164 if x_co > image_size_x - 1:
165 x_co = image_size_x - 1
166 if x_co < 0:
167 x_co = 0
168 if y_co > image_size_y - 1:
169 y_co = image_size_y - 1
170 if y_co < 0:
171 y_co = 0
173 if self.mirror_x:
174 x_co = image_size_x -1 - x_co
176 if self.mirror_y:
177 y_co = image_size_y -1 - y_co
179 col_out = vertex_colors.data[loop].color
181 pixelNumber = (image_size_x * y_co) + x_co
182 r = uv_pixels[pixelNumber*4]
183 g = uv_pixels[pixelNumber*4 + 1]
184 b = uv_pixels[pixelNumber*4 + 2]
185 a = uv_pixels[pixelNumber*4 + 3]
187 col_in = r, g, b # texture-color
188 col_result = [r,g,b] # existing / 'base' color
190 if self.blendingMode == 'MIX':
191 col_result = col_in
193 elif self.blendingMode == 'ADD':
194 col_result[0] = col_in[0] + col_out[0]
195 col_result[1] = col_in[1] + col_out[1]
196 col_result[2] = col_in[2] + col_out[2]
198 elif self.blendingMode == 'SUBTRACT':
199 col_result[0] = col_in[0] - col_out[0]
200 col_result[1] = col_in[1] - col_out[1]
201 col_result[2] = col_in[2] - col_out[2]
203 elif self.blendingMode == 'MULTIPLY':
204 col_result[0] = col_in[0] * col_out[0]
205 col_result[1] = col_in[1] * col_out[1]
206 col_result[2] = col_in[2] * col_out[2]
208 elif self.blendingMode == 'SCREEN':
209 col_result[0] = 1 - (1.0 - col_in[0]) * (1.0 - col_out[0])
210 col_result[1] = 1 - (1.0 - col_in[1]) * (1.0 - col_out[1])
211 col_result[2] = 1 - (1.0 - col_in[2]) * (1.0 - col_out[2])
213 elif self.blendingMode == 'OVERLAY':
214 if col_out[0] < 0.5:
215 col_result[0] = col_out[0] * (2.0 * col_in[0])
216 else:
217 col_result[0] = 1.0 - (2.0 * (1.0 - col_in[0])) * (1.0 - col_out[0])
218 if col_out[1] < 0.5:
219 col_result[1] = col_out[1] * (2.0 * col_in[1])
220 else:
221 col_result[1] = 1.0 - (2.0 * (1.0 - col_in[1])) * (1.0 - col_out[1])
222 if col_out[2] < 0.5:
223 col_result[2] = col_out[2] * (2.0 * col_in[2])
224 else:
225 col_result[2] = 1.0 - (2.0 * (1.0 - col_in[2])) * (1.0 - col_out[2])
227 elif self.blendingMode == 'DIFFERENCE':
228 col_result[0] = fabs(col_in[0] - col_out[0])
229 col_result[1] = fabs(col_in[1] - col_out[1])
230 col_result[2] = fabs(col_in[2] - col_out[2])
232 elif self.blendingMode == 'DIVIDE':
233 if(col_in[0] != 0.0):
234 col_result[0] = col_out[0] / col_in[0]
235 if(col_in[1] != 0.0):
236 col_result[0] = col_out[1] / col_in[1]
237 if(col_in[2] != 0.0):
238 col_result[2] = col_out[2] / col_in[2]
240 elif self.blendingMode == 'DARKEN':
241 if col_in[0] < col_out[0]:
242 col_result[0] = col_in[0]
243 else:
244 col_result[0] = col_out[0]
245 if col_in[1] < col_out[1]:
246 col_result[1] = col_in[1]
247 else:
248 col_result[1] = col_out[1]
249 if col_in[2] < col_out[2]:
250 col_result[2] = col_in[2]
251 else:
252 col_result[2] = col_out[2]
255 elif self.blendingMode == 'LIGHTEN':
256 if col_in[0] > col_out[0]:
257 col_result[0] = col_in[0]
258 else:
259 col_result[0] = col_out[0]
260 if col_in[1] > col_out[1]:
261 col_result[1] = col_in[1]
262 else:
263 col_result[1] = col_out[1]
264 if col_in[2] > col_out[2]:
265 col_result[2] = col_in[2]
266 else:
267 col_result[2] = col_out[2]
269 elif self.blendingMode == 'HUE':
270 hsv_in = rgb_to_hsv(col_in[0], col_in[1], col_in[2])
271 hsv_out = rgb_to_hsv(col_out[0], col_out[1], col_out[2])
272 hue = hsv_in[0]
273 col_result = hsv_to_rgb(hue, hsv_out[1], hsv_out[2])
275 elif self.blendingMode == 'SATURATION':
276 hsv_in = rgb_to_hsv(col_in[0], col_in[1], col_in[2])
277 hsv_out = rgb_to_hsv(col_out[0], col_out[1], col_out[2])
278 sat = hsv_in[1]
279 col_result = hsv_to_rgb(hsv_out[0], sat, hsv_out[2])
281 elif self.blendingMode == 'VALUE':
282 hsv_in = rgb_to_hsv(col_in[0], col_in[1], col_in[2])
283 hsv_out = rgb_to_hsv(col_out[0], col_out[1], col_out[2])
284 val = hsv_in[2]
285 col_result = hsv_to_rgb(hsv_out[0], hsv_out[1], val)
287 elif self.blendingMode == 'COLOR':
288 hsv_in = rgb_to_hsv(col_in[0], col_in[1], col_in[2])
289 hsv_out = rgb_to_hsv(col_out[0], col_out[1], col_out[2])
290 hue = hsv_in[0]
291 sat = hsv_in[1]
292 col_result = hsv_to_rgb(hue, sat, hsv_out[2])
294 elif self.blendingMode == 'SOFT_LIGHT':
295 scr = 1 - (1.0 - col_in[0]) * (1.0 - col_out[0])
296 scg = 1 - (1.0 - col_in[1]) * (1.0 - col_out[1])
297 scb = 1 - (1.0 - col_in[2]) * (1.0 - col_out[2])
299 col_result[0] = (1.0 - col_out[0]) * (col_in[0] * col_out[0]) + (col_out[0] * scr)
300 col_result[1] = (1.0 - col_out[1]) * (col_in[1] * col_out[1]) + (col_out[1] * scg)
301 col_result[2] = (1.0 - col_out[2]) * (col_in[2] * col_out[2]) + (col_out[2] * scb)
304 elif self.blendingMode == 'LINEAR_LIGHT':
305 if col_in[0] > 0.5:
306 col_result[0] = col_out[0] + 2.0 * (col_in[0] - 0.5)
307 else:
308 col_result[0] = col_out[0] + 2.0 * (col_in[0] - 1.0)
309 if col_in[1] > 0.5:
310 col_result[1] = col_out[1] + 2.0 * (col_in[1] - 0.5)
311 else:
312 col_result[1] = col_out[1] + 2.0 * (col_in[1] - 1.0)
313 if col_in[2] > 0.5:
314 col_result[2] = col_out[2] + 2.0 * (col_in[2] - 0.5)
315 else:
316 col_result[2] = col_out[2] + 2.0 * (col_in[2] - 1.0)
318 # Add alpha color
319 a_inverted = 1 - a
320 alpha_color = context.scene.uv_bake_alpha_color
321 col_result = (col_result[0] * a + alpha_color[0] * a_inverted,
322 col_result[1] * a + alpha_color[1] * a_inverted,
323 col_result[2] * a + alpha_color[2] * a_inverted)
325 vertex_colors.data[loop].color = col_result
327 return {'FINISHED'}
329 class VIEW3D_PT_tools_uv_bake_texture_to_vcols(bpy.types.Panel):
330 bl_label = "Bake"
331 bl_space_type = "VIEW_3D"
332 bl_region_type = "TOOLS"
333 bl_options = {'DEFAULT_CLOSED'}
335 @classmethod
336 def poll(self, context):
337 return(context.mode == 'PAINT_VERTEX')
339 def draw(self, context):
340 layout = self.layout
341 col = layout.column()
342 col.prop(context.scene, "uv_bake_alpha_color")
343 col.separator()
344 col.operator("uv.bake_texture_to_vcols", text="UV Texture to VCols")
346 def register():
347 bpy.utils.register_module(__name__)
348 bpy.types.Scene.uv_bake_alpha_color = FloatVectorProperty(
349 name="Alpha Color",
350 description="Color to be used for transparency",
351 subtype='COLOR',
352 min=0.0,
353 max=1.0)
355 def unregister():
356 bpy.utils.unregister_module(__name__)
357 del bpy.types.Scene.uv_bake_alpha_color
359 if __name__ == "__main__":
360 register()