All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.prism.impl.packrect.RectanglePacker Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.prism.impl.packrect;

import com.sun.javafx.geom.Rectangle;
import com.sun.prism.Texture;
import java.util.ArrayList;
import java.util.List;

/**
 * Packs rectangles supplied by the user (typically representing image regions)
 * into a larger backing store rectangle (typically representing a large
 * texture). Supports automatic compaction of the space on the backing store,
 * and automatic expansion of the backing store, when necessary.
 */
public class RectanglePacker {
    /**
     * A reference to the backing store that was created (lazily)
     * by the backing store manager.
     */
    private Texture backingStore;

    /** Manages a list of Levels; this is the core data structure
     contained within the RectanglePacker and encompasses the storage
     algorithm for the contained Rects. */
    // Maintained in sorted order by increasing Y coordinate
    private List levels = new ArrayList(150);
    private static final int MIN_HEIGHT = 8; // The minimum height of level
    private static final int ROUND_UP = 4; // Round up to multiple of 4
    private int recentUsedLevelIndex = 0;
    private int nextAddY;
    private int w;
    private int h;

    /**
     * Creates a new RectanglePacker. You must specify the texture used as the
     * backing store, and the width and height of the space within which rectangles
     * are to be packed.
     *
     * @param backingStore The backing store texture, must not be null
     * @param width The width of the backing store, must be > 0 (typically > 512)
     * @param height The height of the backing store, must be > 0 (typically > 512)
     */
    public RectanglePacker(Texture backingStore, int width, int height) {
        this.backingStore = backingStore;
        this.w = width;
        this.h = height;
    }

    /**
     * Gets a reference to the backing store, creating it lazily if necessary.
     * @return A reference to the backing store.
     */
    public final Texture getBackingStore() {
        return backingStore;
    }

    /**
     * Decides upon an (x, y) position for the given rectangle (leaving
     * its width and height unchanged) and places it on the backing
     * store.
     */
    public final boolean add(Rectangle rect) {
        // N need to continue if the rectangle simply won't fit.
        if (rect.width > w) return false;

        int newHeight = MIN_HEIGHT > rect.height ? MIN_HEIGHT : rect.height;

        // Round up
        newHeight = (newHeight + ROUND_UP - 1) - (newHeight - 1) % ROUND_UP;

        int newIndex;
        // If it does not match recent used level, using binary search to find
        // the best fit level's index
        if (recentUsedLevelIndex < levels.size()
                && levels.get(recentUsedLevelIndex).height != newHeight) {
            newIndex = binarySearch(levels, newHeight);
        } else {
            newIndex = recentUsedLevelIndex;
        }

        // Can create a new level with newHeight
        boolean newLevelFlag = nextAddY + newHeight <= h;

        // Go through the levels check whether we can satisfy the allocation
        // request
        for (int i = newIndex, max = levels.size(); i < max; i++) {
            Level level = levels.get(i);
            // If level's height is more than (newHeight + ROUND_UP * 2) and
            // the cache still has some space left, go create a new level
            if (level.height > (newHeight + ROUND_UP * 2) && newLevelFlag) {
                break;
            } else if (level.add(rect)) {
                recentUsedLevelIndex = i;
                return true;
            }
        }

        // Try to add a new Level.
        if (!newLevelFlag) {
            return false;
        }

        Level newLevel = new Level(w, newHeight, nextAddY);
        nextAddY += newHeight;

        // For a rect that cannot fit into the existing level, create a new
        // level and add at the end of levels that have the same height
        if (newIndex < levels.size() && levels.get(newIndex).height <= newHeight) {
            levels.add(newIndex + 1, newLevel);
            recentUsedLevelIndex = newIndex + 1;
        } else {
            levels.add(newIndex, newLevel);
            recentUsedLevelIndex = newIndex;
        }
        return newLevel.add(rect);
    }

    /**
     * Clears all Rectangles contained in this RectanglePacker.
     */
    public void clear() {
        levels.clear();
        nextAddY = 0;
        recentUsedLevelIndex = 0;
    }

    /**
     * Disposes the backing store allocated by the
     * BackingStoreManager. This RectanglePacker may no longer be used
     * after calling this method.
     */
    public void dispose() {
        if (backingStore != null) {
            backingStore.dispose();
        }

        backingStore = null;
        levels = null;
    }

    /** Using binary search to find the last index of best fit level for k,
     where k is a rounded-up value. */
    private static int binarySearch(List levels, int k) {

        // k+1 is used to find the last index of the level with height of k. Because of rounding up, more
        // likely, there are a bunch of levels have the same height. But, we always keep adding levels and
        // rects at the end. k+1 is a trick to find the last index by finding the next greater value's index
        // and go back one.
        int key = k + 1;
        int from = 0, to = levels.size() - 1;
        int mid = 0;
        int height = 0;

        if (to < 0) {
            return 0;
        }

        while (from <= to) {
            mid = (from + to) / 2;
            height = levels.get(mid).height;
            if (key < height) {
                to = mid - 1;
            } else {
                from = mid + 1;
            }
        }

        if (height < k) {
            return mid + 1;
        } else if (height > k) {
            return mid > 0 ? mid - 1 : 0;
        } else {
            return mid;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy