Example #1
0
def addFlippedImgData(frameData, metaFrame, old_frame, new_frame):
    x_border = old_frame[2][0]
    newMetaFrame = []
    for piece in metaFrame:
        newPiece = MetaFramePiece(piece.imgIndex, piece.attr0, piece.attr1,
                                  piece.attr2)
        newPiece.setHFlip(True)
        # move the piece in the reflected position.
        range = newPiece.GetBounds()
        endX = range[2]
        origEndX = x_border + endX
        flipStartX = -origEndX + old_frame[0].size[0]
        newX = flipStartX - x_border
        newPiece.setXOffset(newX)
        newMetaFrame.append(newPiece)

    # regardless of flip, the center point may be treated differently between frames.  correct it.
    point_diff = exUtils.addLoc(new_frame[2], old_frame[2], True)
    # add it to all metaframe components
    for piece in newMetaFrame:
        new_offset = (piece.getXOffset() - point_diff[0],
                      piece.getYOffset() - point_diff[1])
        piece.setXOffset(new_offset[0])
        piece.setYOffset(new_offset[1])
    frameData.append(newMetaFrame)
Example #2
0
def addImgData(imgData, frameData, palette_map, transparent, frame):
    img = frame[0]
    pt_zero = frame[2]
    # chop the frames into images and metaframes - need psy's algorithm for this
    piece_locs = chopImgToPieceLocs(img, transparent)

    metaFrame = []
    # create new metaframe piece data from the pieces
    cur_tile = 0
    for idx, piece_loc in enumerate(piece_locs):
        piece = piece_loc[0]
        loc = piece_loc[1]
        metaFramePiece = MetaFramePiece(len(imgData) + idx, 0, 0, 0)
        # set coordinates
        result_loc = exUtils.addLoc(loc, pt_zero, True)
        metaFramePiece.setXOffset(result_loc[0])
        metaFramePiece.setYOffset(result_loc[1])
        # set dimensions
        block_size = (piece.size[0] // TEX_SIZE, piece.size[1] // TEX_SIZE)
        res_type = DIM_TABLE.index(block_size)
        metaFramePiece.setResolutionType(res_type)
        # set RnS parameter - always true when not disabled; when reading in we are never disabled
        metaFramePiece.setRotAndScalingOn(True)
        # set tile index
        metaFramePiece.setTileNum(cur_tile)
        # priority is ALWAYS 3
        metaFramePiece.setPriority(3)
        # set last if this is the last
        if idx == len(piece_locs) - 1:
            metaFramePiece.setIsLast(True)

        metaFrame.append(metaFramePiece)
        # increase tile index
        blocks_occupied = max(1, block_size[0] * block_size[1] // 4)
        cur_tile += blocks_occupied
    frameData.append(metaFrame)

    # add each piece
    for piece, _ in piece_locs:
        imgStrip = convertPieceToImgStrip(piece, palette_map)
        imgData.append(imgStrip)
Example #3
0
def ImportSheets(inDir, strict=False):

    if DEBUG_PRINT:
        if not os.path.isdir(os.path.join(inDir, '_pieces_in')):
            os.makedirs(os.path.join(inDir, '_pieces_in'))
        if not os.path.isdir(os.path.join(inDir, '_frames_in')):
            os.makedirs(os.path.join(inDir, '_frames_in'))

    anim_stats = {}
    anim_names = {}
    tree = ET.parse(os.path.join(inDir, 'AnimData.xml'))
    root = tree.getroot()
    sdwSize = int(root.find('ShadowSize').text)
    if sdwSize < 0 or sdwSize > 2:
        raise ValueError("Invalid shadow size: {0}".format(sdwSize))
    anims_node = root.find('Anims')
    for anim_node in anims_node.iter('Anim'):
        name = anim_node.find('Name').text
        index = -1
        index_node = anim_node.find('Index')
        if index_node is not None:
            index = int(index_node.text)
        backref_node = anim_node.find('CopyOf')
        if backref_node is not None:
            backref = backref_node.text
            anim_stat = AnimStat(index, name, None, backref)
        else:
            frame_width = anim_node.find('FrameWidth')
            frame_height = anim_node.find('FrameHeight')
            anim_stat = AnimStat(
                index, name, (int(frame_width.text), int(frame_height.text)),
                None)

            rush_frame = anim_node.find('RushFrame')
            if rush_frame is not None:
                anim_stat.rushFrame = int(rush_frame.text)
            hit_frame = anim_node.find('HitFrame')
            if hit_frame is not None:
                anim_stat.hitFrame = int(hit_frame.text)
            return_frame = anim_node.find('ReturnFrame')
            if return_frame is not None:
                anim_stat.returnFrame = int(return_frame.text)

            durations_node = anim_node.find('Durations')
            for dur_node in durations_node.iter('Duration'):
                duration = int(dur_node.text)
                anim_stat.durations.append(duration)

            anim_names[name.lower()] = index
            if index == -1 and strict:
                raise ValueError(
                    "{0} has its own sheet and does not have an index!".format(
                        name))

        if index > -1:
            if index in anim_stats:
                raise ValueError(
                    "{0} and {1} both have the an index of {2}!".format(
                        anim_stats[index].name, name, index))
            anim_stats[index] = anim_stat

    copy_indices = {}
    for idx in anim_stats:
        stat = anim_stats[idx]
        if stat.backref is not None:
            back_idx = anim_names[stat.backref.lower()]
            if back_idx not in copy_indices:
                copy_indices[back_idx] = []
            copy_indices[back_idx].append(idx)

    # read all sheets
    extra_sheets = []
    anim_sheets = {}
    for filepath in glob.glob(os.path.join(inDir, '*-Anim.png')):
        _, file = os.path.split(filepath)
        anim_parts = file.split('-')
        anim_name = anim_parts[0]
        if anim_name.lower() not in anim_names:
            extra_sheets.append(anim_name)
        else:
            index = anim_names[anim_name.lower()]
            del anim_names[anim_name.lower()]

            anim_img = Image.open(os.path.join(inDir, anim_name +
                                               '-Anim.png')).convert("RGBA")
            offset_img = Image.open(
                os.path.join(inDir,
                             anim_name + '-Offsets.png')).convert("RGBA")
            shadow_img = Image.open(
                os.path.join(inDir, anim_name + '-Shadow.png')).convert("RGBA")

            anim_sheets[index] = (anim_img, offset_img, shadow_img, anim_name)

    # raise warning if there exist anim stats without anims, or anims without anim stats
    if len(anim_names) > 0:
        orphans = []
        for k in anim_names:
            orphans.append(k)
        raise ValueError("Xml found with no sheet: {0}".format(
            ', '.join(orphans)))
    if len(extra_sheets) > 0:
        raise ValueError("Sheet found with no xml: {0}".format(
            ', '.join(extra_sheets)))

    animGroupData = []
    frames = []
    frameToSequence = []
    for idx in range(MAX_ANIMS):
        if idx in anim_sheets:
            anim_img, offset_img, shadow_img, anim_name = anim_sheets[idx]
            tileSize = anim_stats[idx].size
            durations = anim_stats[idx].durations

            # check against inconsistent sizing
            if anim_img.size != offset_img.size or anim_img.size != shadow_img.size:
                raise ValueError(
                    "Anim, Offset, and Shadow sheets for {0} must be the same size!"
                    .format(anim_name))

            if anim_img.size[0] % tileSize[0] != 0 or anim_img.size[
                    1] % tileSize[1] != 0:
                raise ValueError(
                    "Sheet for {4} is {0}x{1} pixels and is not divisible by {2}x{3} in xml!"
                    .format(anim_img.size[0], anim_img.size[1], tileSize[0],
                            tileSize[1], anim_name))

            total_frames = anim_img.size[0] // tileSize[0]
            # check against inconsistent duration counts
            if total_frames != len(durations):
                raise ValueError(
                    "Number of frames in {0} does not match count of durations ({1}) specified in xml!"
                    .format(anim_name, len(durations)))

            if anim_stats[idx].rushFrame >= len(durations):
                raise ValueError(
                    "RushFrame of {0} is greater than the number of frames ({1}) in {2}!"
                    .format(anim_stats[idx].rushFrame, len(durations),
                            anim_name))
            if anim_stats[idx].hitFrame >= len(durations):
                raise ValueError(
                    "HitFrame of {0} is greater than the number of frames ({1}) in {2}!"
                    .format(anim_stats[idx].hitFrame, len(durations),
                            anim_name))
            if anim_stats[idx].returnFrame >= len(durations):
                raise ValueError(
                    "ReturnFrame of {0} is greater than the number of frames ({1}) in {2}!"
                    .format(anim_stats[idx].returnFrame, len(durations),
                            anim_name))

            group = []
            total_dirs = anim_img.size[1] // tileSize[1]
            for dir in range(8):
                if dir >= total_dirs:
                    break
                sequence = []
                for jj in range(anim_img.size[0] // tileSize[0]):
                    rel_center = (tileSize[0] // 2 - DRAW_CENTER_X,
                                  tileSize[1] // 2 - DRAW_CENTER_Y)
                    tile_rect = (jj * tileSize[0], dir * tileSize[1],
                                 tileSize[0], tileSize[1])
                    tile_bounds = (tile_rect[0], tile_rect[1],
                                   tile_rect[0] + tile_rect[2],
                                   tile_rect[1] + tile_rect[3])
                    bounds = exUtils.getCoveredBounds(anim_img, tile_bounds)
                    emptyBounds = False
                    if bounds[0] >= bounds[2]:
                        bounds = (rel_center[0], rel_center[1],
                                  rel_center[0] + 1, rel_center[1] + 1)
                        emptyBounds = True
                    rect = (bounds[0], bounds[1], bounds[2] - bounds[0],
                            bounds[3] - bounds[1])
                    abs_bounds = exUtils.addToBounds(
                        bounds, (tile_rect[0], tile_rect[1]))
                    frame_tex = anim_img.crop(abs_bounds)

                    shadow_offset = exUtils.getOffsetFromRGB(
                        shadow_img, tile_bounds, False, False, False, False,
                        True)
                    frame_offset = exUtils.getOffsetFromRGB(
                        offset_img, tile_bounds, True, True, True, True, False)
                    offsets = FrameOffset(None, None, None, None)
                    if frame_offset[2] is None:
                        # raise warning if there's missing shadow or offsets
                        if strict:
                            raise ValueError(
                                "No frame offset found in frame {0} for {1}".
                                format((jj, dir), anim_name))
                        offsets = FrameOffset(rel_center, rel_center,
                                              rel_center, rel_center)
                    else:
                        offsets.center = frame_offset[2]
                        if frame_offset[0] is None:
                            offsets.head = frame_offset[2]
                        else:
                            offsets.head = frame_offset[0]
                        offsets.lhand = frame_offset[1]
                        offsets.rhand = frame_offset[3]
                    offsets.AddLoc((-rect[0], -rect[1]))

                    shadow = rel_center
                    if shadow_offset[4] is not None:
                        shadow = shadow_offset[4]
                    elif strict:
                        raise ValueError(
                            "No shadow offset found in frame {0} for {1}".
                            format((jj, dir), anim_name))
                    shadow_diff = exUtils.addLoc(shadow, rect, True)
                    shadow = exUtils.addLoc(shadow, rel_center, True)

                    if emptyBounds and shadow_offset[
                            4] is None and frame_offset[2] is None:
                        continue

                    frames.append((frame_tex, offsets, shadow_diff))
                    frame = SequenceFrame(-1, durations[jj], 0, shadow, shadow)
                    if anim_stats[idx].rushFrame == jj:
                        frame.SetRushPoint(True)
                    if anim_stats[idx].hitFrame == jj:
                        frame.SetHitPoint(True)
                    if anim_stats[idx].returnFrame == jj:
                        frame.SetReturnPoint(True)

                    sequence.append(frame)
                    frameToSequence.append((idx, dir, jj))

                group.append(sequence)

            animGroupData.append(group)
        else:
            animGroupData.append([])

    # get all unique frames and map them to the animations
    # same with shadows and offsets
    frame_map = [None] * len(frames)
    final_frames = []
    mapDuplicateImportImgs(frames, final_frames, frame_map)

    # center the frame based on shadow placements
    reverse_frame_map = {}
    for idx, mapping in enumerate(frame_map):
        dest = mapping[0]
        if dest not in reverse_frame_map:
            reverse_frame_map[dest] = []
        reverse_frame_map[dest].append(idx)

    for key in reverse_frame_map:
        shadow_diffs = []
        for start in reverse_frame_map[key]:
            new_diff = frames[start][2]
            shadow_diffs.append(new_diff)
        # choose the mode?  median? as the true offset
        freq = {}
        for diff in shadow_diffs:
            if diff not in freq:
                freq[diff] = 0
            freq[diff] += 1
        chosen_diff = shadow_diffs[0]
        for diff in freq:
            if freq[diff] > freq[chosen_diff]:
                chosen_diff = diff
        # now that we have our chosen diff
        # set the frame in final_frames to the chosen diff
        final_frames[key] = (final_frames[key][0], final_frames[key][1],
                             chosen_diff, final_frames[key][3])
        # and then set the diff mapping to their shadow diff - chosen diff
        for start in reverse_frame_map[key]:
            frame_map[start] = (frame_map[start][0],
                                exUtils.addLoc(chosen_diff, frames[start][2],
                                               True))
        # now, the frame will treat chosenDiff as its center
        # and all diffs will be applied to the offsets of the currently created animGroups

    # final_frames is now a list of unique graphics where flips are treated as separate but refer to the originals
    # now, create metaframes and image data
    # palette is needed first.  get palette data

    # use tilequant to modify all anim images at once and force to 16 colors or less (including transparent)
    # first, generate an image containing all frames in final_frames

    max_width = 0
    max_height = 0
    for frame in final_frames:
        frame_tex = frame[0]
        max_width = max(max_width, frame_tex.size[0])
        max_height = max(max_height, frame_tex.size[1])

    max_width = exUtils.roundUpToMult(max_width, 2)
    max_height = exUtils.roundUpToMult(max_height, 2)

    max_tiles = int(math.ceil(math.sqrt(len(final_frames))))
    combinedImg = Image.new('RGBA',
                            (max_tiles * max_width, max_tiles * max_height),
                            (0, 0, 0, 0))

    crop_bounds = []
    for idx, frame in enumerate(final_frames):
        frame_tex = frame[0]
        round_width = exUtils.roundUpToMult(frame_tex.size[0], 2)
        round_height = exUtils.roundUpToMult(frame_tex.size[1], 2)
        tile_pos = (idx % max_tiles * max_width, idx // max_tiles * max_height)
        paste_bounds = (tile_pos[0] + (max_width - round_width) // 2,
                        tile_pos[1] + (max_height - round_height) // 2,
                        tile_pos[0] + (max_width - round_width) // 2 +
                        frame_tex.size[0], tile_pos[1] +
                        (max_height - round_height) // 2 + frame_tex.size[1])
        crop_bounds.append(paste_bounds)
        combinedImg.paste(frame_tex, paste_bounds, frame_tex)

    colors = combinedImg.getcolors()

    if strict and len(colors) > 16:
        raise ValueError(
            "Number of (nontransparent) colors over 15: {0}".format(
                len(colors)))

    transparent = (0, 127, 151, 255)
    foundTrans = True
    while foundTrans:
        foundTrans = False
        for count, color in colors:
            if color == transparent:
                transparent = (0, 127, transparent[3] - 1, 255)
                foundTrans = True
                break

    datas = combinedImg.getdata()
    return_datas = []
    for idx in range(len(datas)):
        if datas[idx][3] == 0:
            return_datas.append(transparent)
        else:
            return_datas.append(datas[idx])
    combinedImg.putdata(return_datas)

    # TODO: wan can actually handle more than 16 colors so long as a single image piece itself only has 16 colors
    # to actually allow over 16 colors, an algorithm would be needed to get the color list for every individual frame
    # and then combine the color lists such that there are as few distinct palettes as possible
    # and that no palettes have over 16 colors (transparency included)

    # then, run through simple_quant
    reducedImg = simple_quant(combinedImg).convert("RGBA")

    datas = reducedImg.getdata()
    for idx in range(len(datas)):
        if return_datas[idx] != transparent:
            return_datas[idx] = datas[idx]
    reducedImg.putdata(return_datas)

    palette = reducedImg.getcolors()
    palette_map = {}
    singlePalette = []
    for count, rgba in palette:
        palette_map[rgba] = len(singlePalette)
        singlePalette.append(rgba)

        # transparent is always 0
        if rgba == transparent:
            prev_zero = singlePalette[0]
            singlePalette[0] = rgba
            singlePalette[-1] = prev_zero
            palette_map[prev_zero] = palette_map[rgba]
            palette_map[rgba] = 0

    # then, cut up the image and return to the original final_frames
    for idx in range(len(final_frames)):
        frame_tex, offsets, shadow_diff, flip = final_frames[idx]
        frame_tex = reducedImg.crop(crop_bounds[idx])
        final_frames[idx] = (frame_tex, offsets, shadow_diff, flip)

    while len(singlePalette) < 16:
        singlePalette.append((32, 169, 32, 255))
        singlePalette.append((65, 117, 100, 255))
        singlePalette.append((105, 110, 111, 255))
        singlePalette.append((32, 50, 48, 255))
        singlePalette.append((50, 49, 32, 255))
        while len(singlePalette) > 16:
            singlePalette.pop()

    # create imgData, metaframe data, and offset data
    imgData = []
    frameData = []
    offsetData = []
    for idx, frame in enumerate(final_frames):
        if DEBUG_PRINT:
            frame[0].save(
                os.path.join(inDir, '_frames_in',
                             'F-' + format(idx, '02d') + '.png'))

        shadow_diff = frame[2]
        flip = frame[3]
        if flip > -1:
            flipped_frame = frameData[flip]
            addFlippedImgData(frameData, flipped_frame, final_frames[flip],
                              frame)
        else:
            # will append to imgData and frameData
            addImgData(imgData, frameData, palette_map, transparent, frame)
        offsets = frame[1]
        offsets.AddLoc((-shadow_diff[0], -shadow_diff[1]))
        offsetData.append(offsets)

    # apply the mappings to the animations, correcting the frame indices and shadow offsets
    for idx, frame in enumerate(frames):
        frame_seq_mapping = frameToSequence[idx]
        group = animGroupData[frame_seq_mapping[0]]
        sequence = group[frame_seq_mapping[1]]
        animFrame = sequence[frame_seq_mapping[2]]
        mapFrame = frame_map[idx]
        animFrame.frameIndex = mapFrame[0]
        shadow_diff = mapFrame[1]
        animFrame.offset = exUtils.addLoc(animFrame.offset, shadow_diff)

    for idx in copy_indices:
        copies = copy_indices[idx]
        for copy_idx in copies:
            animGroupData[copy_idx] = exWanUtils.duplicateAnimGroup(
                animGroupData[idx])

    wan = WanFile()
    wan.imgData = imgData
    wan.frameData = frameData
    wan.animGroupData = animGroupData
    wan.offsetData = offsetData
    wan.customPalette = [singlePalette]
    wan.sdwSize = sdwSize
    # return the wan file
    return wan
Example #4
0
 def AddLoc(self, loc):
     self.head = exUtils.addLoc(self.head, loc)
     self.lhand = exUtils.addLoc(self.lhand, loc)
     self.rhand = exUtils.addLoc(self.rhand, loc)
     self.center = exUtils.addLoc(self.center, loc)