def get_image_from_texture2d(texture_2d, flip=True) -> Image: """converts the given texture into PIL.Image :param texture_2d: texture to be converterd :type texture_2d: Texture2D :param flip: flips the image back to the original (all Unity textures are flipped by default) :type flip: bool :return: PIL.Image object :rtype: Image """ image_data = copy(bytes(texture_2d.image_data)) if not image_data: return Image.new("RGB", (0, 0)) texture_format = ( texture_2d.m_TextureFormat if isinstance(texture_2d.m_TextureFormat, TF) else TF(texture_2d.m_TextureFormat) ) selection = CONV_TABLE[texture_format] if len(selection) == 0: raise NotImplementedError( f"Not implemented texture format: {texture_format.name}" ) if texture_format in XBOX_SWAP_FORMATS: image_data = swap_bytes_for_xbox(image_data, texture_2d.platform) if "Crunched" in texture_format.name: version = texture_2d.version if ( version[0] > 2017 or (version[0] == 2017 and version[1] >= 3) # 2017.3 and up or texture_format == TF.ETC_RGB4Crunched or texture_format == TF.ETC2_RGBA8Crunched ): image_data = texture2ddecoder.unpack_unity_crunch(image_data) else: image_data = texture2ddecoder.unpack_crunch(image_data) img = selection[0]( image_data, texture_2d.m_Width, texture_2d.m_Height, *selection[1:] ) if img and flip: return img.transpose(Image.FLIP_TOP_BOTTOM) return img
def _test_crunched(name, func, unity=False): # load sample data data: bytes = zip.open(name + ".data", "r").read() details: dict = json.loads(zip.open(name + ".json", "r").read()) ori_img: Image = Image.open(zip.open(name + ".png", "r")) if unity: data = texture2ddecoder.unpack_unity_crunch(data) else: data = texture2ddecoder.unpack_crunch(data) # decompress data width = details["m_Width"] height = details["m_Height"] dec = func(data, width, height) # load raw image data dec_img = Image.frombytes("RGBA", (width, height), dec, 'raw', ("BGRA")) dec_img = dec_img.convert(ori_img.mode) # compare images assert (ImageChops.difference(ori_img, dec_img).getbbox() is None)
def get_resource(self, path_id: int): obj = self.asset.objects[path_id] if obj.type == 'Texture2D': data = obj.read() if len(data.data) == 0: logger.warning("Data doesn't exist") return Resource() # logger.warning(f"{self.container[path_id]} is {data.format.name}") if data.format.name == 'ETC_RGB4': # 이미지 포맷: ETC1 -> RGB(A) # Alpha 채널도 나오긴 하는데 의미 없어서 자름 decoded = texture2ddecoder.decode_etc1(data.data, data.width, data.height) im = np.frombuffer(decoded, np.uint8).reshape(data.height, data.width, 4) im = cv2.flip(im, 0) im_a = self.get_alpha_image(path_id) # if we have alpha, try to merge if im_a is not None: # 알파 채널 이미지가 원래 이미지와 크기가 다른 경우가 있는 경우를 위한 리사이징 if im_a.shape[:2] != (data.height, data.width): im_a = cv2.resize(im_a, None, fx=data.height / im_a.shape[0], fy=data.width / im_a.shape[1]) im[:, :, 3] = im_a return ResImage(im, data.name) else: return ResImage(cv2.cvtColor(im, cv2.COLOR_BGRA2BGR), data.name) elif data.format.name == 'ETC_RGB4Crunched': decrunched = texture2ddecoder.unpack_unity_crunch(data.data) # 이미지 포맷: ETC1 -> RGB(A) # Alpha 채널도 나오긴 하는데 의미 없어서 자름 decoded = texture2ddecoder.decode_etc1(decrunched, data.width, data.height) im = np.frombuffer(decoded, np.uint8).reshape(data.height, data.width, 4) im = cv2.flip(im, 0) # attempt to find alpha image im_a = self.get_alpha_image(path_id) # if we have alpha, try to merge if im_a is not None: # 알파 채널 이미지가 원래 이미지와 크기가 다른 경우가 있는 경우를 위한 리사이징 if im_a.shape[:2] != (data.height, data.width): im_a = cv2.resize(im_a, None, fx=data.height / im_a.shape[0], fy=data.width / im_a.shape[1]) im[:, :, 3] = im_a return ResImage(im, data.name) else: return ResImage(cv2.cvtColor(im, cv2.COLOR_BGRA2BGR), data.name) elif data.format.name == "ETC2_RGBA8": decoded = texture2ddecoder.decode_etc2a8( data.data, data.width, data.height) im = np.frombuffer(decoded, np.uint8).reshape(data.height, data.width, 4) return ResImage(cv2.flip(im, 0), data.name) elif data.format.name == 'ETC2_RGBA8Crunched': decrunched = texture2ddecoder.unpack_unity_crunch(data.data) decoded = texture2ddecoder.decode_etc2a8( decrunched, data.width, data.height) im = np.frombuffer(decoded, np.uint8).reshape(data.height, data.width, 4) return ResImage(cv2.flip(im, 0), data.name) elif data.format.name == 'RGBA32': data_size = data.height * data.width * 4 # 이미지 포맷: RGBA32 -> RGBA im = np.frombuffer(data.data[:data_size], dtype=np.uint8).reshape( data.height, data.width, 4) # cv2에서는 BGRA 색역을 쓰기 때문에 변한 im = cv2.cvtColor(im, cv2.COLOR_RGBA2BGRA) # 소전은 뒤집힌거 쓰니까 이미지 뒤집기 im = cv2.flip(im, 0) return ResImage(im, data.name) elif data.format.name == 'RGBA4444': shape = (data.height, data.width) data_size = data.height * data.width * 2 # numpy array로 변환 (아직 1차원 배열) im_array = np.frombuffer(data.data[:data_size], dtype=np.uint8) # 비트 시프트 + 값 곱하기 후 3차원 배열로 만든 후 채널별로 분리 # 0x0 -> 0x00, 0x1 -> 0x11, ... , 0xF -> 0xFF im_b, im_r = cv2.split( (np.bitwise_and(im_array >> 4, 0x0f) * 17).reshape( *shape, 2)) im_a, im_g = cv2.split( (np.bitwise_and(im_array, 0x0f) * 17).reshape(*shape, 2)) return ResImage( cv2.flip(cv2.merge((im_b, im_g, im_r, im_a)), 0), data.name) elif data.format.name == 'ARGB4444': shape = (data.height, data.width) data_size = data.height * data.width * 2 # numpy array로 변환 (아직 1차원 배열) im_array = np.frombuffer(data.data[:data_size], dtype=np.uint8) # 비트 시프트 + 값 곱하기 후 3차원 배열로 만든 후 채널별로 분리 # 0x0 -> 0x00, 0x1 -> 0x11, ... , 0xF -> 0xFF im_g, im_a = cv2.split( (np.bitwise_and(im_array >> 4, 0x0f) * 17).reshape( *shape, 2)) im_b, im_r = cv2.split( (np.bitwise_and(im_array, 0x0f) * 17).reshape(*shape, 2)) return ResImage( cv2.flip(cv2.merge((im_b, im_g, im_r, im_a)), 0), data.name) elif data.format.name == 'ARGB32': # 이미지 포맷: ARGB32 -> RGBA # 채널별로 분리 후 다시 합침 # 이상하게 원래 크기 이상으로 뭔가 데이터가 있는데 딱히 필요는 없어서 모양으로 계산해서 필요한 부분만 슬라이싱 data_size = data.height * data.width * 4 im_array = np.frombuffer(data.data[:data_size], dtype=np.uint8).reshape( data.height, data.width, 4) im_array = cv2.cvtColor(cv2.flip(im_array, 0), cv2.COLOR_RGBA2BGRA) return ResImage(im_array, data.name) elif data.format.name == 'Alpha8': # 알파 이미지 shape = (data.height, data.width) return ResImage( cv2.flip( np.frombuffer(data.data, dtype=np.uint8).reshape(shape), 0), data.name) elif data.format.name == 'RGB24': # 이미지 포맷: RGB24 -> RGB # 간단하게 처리 data_size = data.height * data.width * 3 im_rgb = np.frombuffer(data.data[:data_size], dtype=np.uint8).reshape( data.height, data.width, 3) # attempt to find alpha image im_a = self.get_alpha_image(path_id) if im_a is not None: # 알파 채널 이미지가 원래 이미지와 크기가 다른 경우가 있는 경우를 위한 리사이징 if im_a.shape[:2] != (data.height, data.width): im_a = cv2.resize(im_a, None, fx=data.height / im_a.shape[0], fy=data.width / im_a.shape[1]) im_a = cv2.flip( im_a, 0 ) # will end up flipping alpha twice below.. is there a better way? im_bgra = cv2.cvtColor(im_rgb, cv2.COLOR_RGB2BGRA) try: im_bgra[:, :, 3] = im_a except ValueError: # skip alpha if we can't merge it back, for some odd reason this happens on certain files # (cafe chair texture.. etc) as of Dec 22, 2018 on both TW, and CN beta version logger.warning( "dimension mismatch between converted rgb/alpha data" ) return ResImage(cv2.flip(im_bgra, 0), data.name) else: return ResImage( cv2.flip(cv2.cvtColor(im_rgb, cv2.COLOR_RGB2BGR), 0), data.name) elif data.format.name == 'RGB565': im_array = np.frombuffer(data.data, dtype=np.uint8).reshape( data.height, data.width, 2) return ResImage( cv2.flip(cv2.cvtColor(im_array, cv2.COLOR_BGR5652BGR), 0), data.name) elif data.format.name == 'PVRTC_RGBA4' or data.format.name == 'PVRTC_RGBA2': decoded = texture2ddecoder.decode_pvrtc( data.data, data.width, data.height) im = np.frombuffer(decoded, np.uint8).reshape(data.height, data.width, 4) return ResImage(cv2.flip(im, 0), data.name) else: # 모르는 포맷은 그냥 건너뜀 raise Exception('Unexpected texture format ' + str(data.format)) return Resource(data.name) elif obj.type == 'TextAsset': data = obj.read() # is there a better way? if "/live2d/" in self.container.get(path_id): return ResL2D(data.bytes, data.name) elif isinstance(data.script, str): # 데이터가 일반 텍스트(txt)인경우 str 로 리턴 return ResText(data.script, data.name) else: # 아니면 Bytes 형태로 리턴 return ResBytes(data.bytes, data.name) else: return Resource()