|
|
|
|
James
Russell
Johnathan
Broadway
Pretty Pictures Project
Specification
OVERVIEW
HIGH
LEVEL SPECIFICATION
PrettyPictures Project Overview
The
project goal in designing and implementing ‘Pretty Pictures’ is to have a
program that manipulates images on a pixel by pixel basis using mathematical
formulas and user-defined inputs. The
user can then select his/her favorite of these generated images and ‘breed’
these images to produce a new generation of images which inherit
characteristics of the chosen images from the previous generation. After several iterations the program is
expected to produce images that are aesthetically pleasing to the user. These images can then be saved for human
consumption.
Program
Use and
The
program can be executed by opening with the Java 6th Edition, or by using the
command prompt. The following command
would run the program assuming the current directory contains the file.
$ java PrettyPictures
The
program will display a GUI interface which will allow for
user-interaction. An example of the
user-interface is shown below.

The
program will start by generating a series of random functions which will be
used to create the initial generation of images. These images are loaded concurrently. Once these images are loaded, the GUI will
allow for the following actions:
SELECTION/DESELECTION:
Selecting
preferred images is accomplished by double clicking on the desired image or
selecting the image and pressing the ‘add’ button, represented by a green
‘plus’ sign. This will generate an
identical thumbnail in the lower selection panel. This thumbnail can then be de-selected by
double clicking the replicated thumbnail or by selecting the thumbnail and
clicking the ‘remove’ button, represented by a red ‘minus’ sign.
GENERATION
HISTORY NAVIGATION:
The user
will be able to navigate between previous generations of images via the use of
“Back” and “Forward” buttons similar to those found in web browsers. Using these buttons does not remove the
selected images that are in the lower selection panel, allowing for images from
previous generations to be bred with images from the current generation. A drop-menu “History” is also available,
which acts in a manner identical to a standard web browser. This is accessed using the ‘history’ button,
represented as a folder near the stop and restart buttons.
STOP AND
RESTART:
The user
will be able to stop all computation, as well as throw away and restart all
computation that has been completed for the current generation. This will be
accomplished with “Stop” and “Restart” buttons, represented by standard ‘stop’
and ‘refresh’ icons as used by web browsers.
A small icon in the right hand side will indicate that the program is
currently loading images by becoming an animated icon. It will revert to a standard still icon when
finished.
ZOOM
FOCUS:
The user
will be able to display a single image at a higher resolution than the
thumbnails by use of a “Zoom” button.
This will load a page that features only the image. On this page the user will be able to set the
generated resolution and save the image if desired. An example of the zoom page is shown below.
On this
panel the user will be able to modify the formula for a given image. The format is rigidly specified as follows:
FunctionName(ChildFunction)
The
following inputs are available for user input:
AbsVal(ChildFunction)
Add(ChildFunction1,
ChildFunction2)
ATan(ChildFunction)
Clip(ChildFunction)
ColorPerlinNoise(ChildFunction)
Constant[xr yg zb] (x, y, z
are decimal values between -1, 1)
Cos(ChildFunction)
Divide(ChildFunction1,
ChildFunction2)
Exponentiate(ChildFunction) (Power will be randomly
generated up to 5)
GrayScalePerlinNoise(ChildFunction)
LCToRGB(ChildFunction)
Log(ChildFunction)
Multiply(ChildFunction1,
ChildFunction2)
Negate(ChildFunction)
RGBToLC(ChildFunction)
RoundDown(ChildFunction)
RoundUp(ChildFunction)
Sin(ChildFunction)
Subtract(ChildFunction1,
ChildFunction2)
Wrap(ChildFunction)
X[xr yg zb] (x, y, z
are decimal values)
Y[xr yg zb] (x, y, z
are decimal values)
Once the
formula is modified, the user may press the ‘Change Function’ icon, represented
by a small gear. If the modification is
accepted, the image will change. If it
fails, the user will be notified.

SAVING
AND OPENING IMAGES:
The user
will be able to save images through use of the ‘Zoom’ function. Images can be opened into the current
generation by pressing the ‘Open’ button and selecting a valid image file. A thumbnail of this image will then be loaded
into the current generation. The images
can be saved as either a .PNG file or as a .PRP file, which can then be
loaded. This file contains the formula
for the image and not the image itself, so it takes up very little disk space.
ANIMATION:
The user
will be able to select a pair of images and ‘animate’ between them. If these images are of similar genotype
(explained later), the images will fade from one to another very smoothly. If they are of non-similar genotype, the
result will be much less smooth. This
animation can be previewed and saved in PNG format. Animation is accomplished through use of the
‘animate’ button, represented by a light-blue/blue circle which fades from one
color to another.
The
Animation page will allow for the user to fine tune the animation speed and
number of frames. The animation can also
be saved as a series of PNG files. An example of the Animation page is shown
below.

BREEDING
GENERATIONS:
After
selecting the images that the user finds aesthetically pleasing, he/she may
then ‘breed’ a new generation of images.
By pressing the ‘breed’ button, represented by a green circle inset with
a right-facing triangle, the selected images will be compared to each other and
the formulas that define them are ‘bred’ using a specialized genetic algorithm:
each of the pictures is compared to another and the formulas are mingled
together in a manner that depends on how similar they are. The end result will produce an image that is
a mix of the two images. This is
repeated among the selected images (with pairings chosen at random) until a new
generation is completely generated. The
selected images are then removed from the selection panel and the process
repeats itself.
On the
lower left panel of the GUI, a set of sliders is available to the user. They are labeled as ‘Red Saturation’, ‘Green
Saturation’, ‘Blue Saturation’, and ‘Mutation Percentage’. These saturation sliders can be used to
adjust the overall color of an image while rendering. The adjustment takes effect before pressing
restart, breed random generation, breed next generation, zoom, and
animate. These sliders do not change the
underlying function, they merely adjust it’s
appearance on screen.
The mutation slider is used to adjust the chance that a function has to
‘mutate’ during breeding. This mutation
is (mostly) random, and means that an image can breed with itself if the
mutation is set high and produce a very different image.
LOW
LEVEL SPECIFICATION
Functions
and function representation
The
functions that define the images are binary trees built with nodes that are
either Zero, Single, or Dual Child functions. All functions take in arguments X
and Y (pixel coordinates) and return a three dimensional vector of RGB values.
To translate pixel coordinates into RGB values, functions will perform
operations on those coordinates as well as the function’s child functions, if they
are present. ZeroChildFunctions have no children on
which to operate, so they return constants or values derived from the XY
coordinates. SingleChildFunctions have one child
function, and DualChildFunctions have two.
As an
example of the representation of a function, consider f(X,Y)
= cos(3+X).
This
function would be represented as a SingleChildFunction
that returns the value of cos(CHILD_0), where CHILD_0 is a DualChildFunction
that returns the value of CHILD_1 + CHILD_2. CHILD_1 and CHILD_2 would both be ZeroChildFunctions that return the values of 3 and X,
respectively.
A
function’s operations are determined by its state. The returnValue(function host, int
x, int y) method of a function will delegate to the function’s
state. The state will perform any manipulation of the x, y coordinates or the
host’s children’s return values that are necessary and return the result.
The returnValue()
method in a function can also take one or two Vectors as arguments (via
variable arguments). If these arguments are present, it treats them as the
output of its children function(s) rather than the actual output. For instance,
a Dual Child Function with the
Only the
Zero Child Functions with the
Functions
and function states contain Strings representing the “path” to the function
(example: “model.DualChildFunction” or “model.AddState”). This String is used for dynamic class
loading of the functions as well as equality testing. If two functions or
states have the same path, then they must be instances of the same class.
Generating random functions and
function trees
To
generate random functions, we maintain arrays of the names of each type of
function. To generate a random function, we index into the array with a random
number and use dynamic class loading to create an instance of the proper
function. Constants, exponents, and other private fields are set to random
values by the default constructors of the functions that possess them.
To create
random function trees, we first randomly generate the root node and add it to a
queue. Then, while the queue is not empty, we pop the first function from the
queue and check to see if it is a Dual, Single, or Zero child function. If it
is Dual or Single, we create two or one new random
functions, set them as the current function’s children, and add them to the
queue. If it is a Zero child function, we do nothing. When the queue is empty,
the function tree has been fully generated.
Psuedocode for random function tree generation:
rootNode = new randomFunction
FuncQ.add(rootNode)
While(FuncQ not empty)
{
currentFunction
= FuncQ.pop()
if (currentFunction is a Dual Child Function){
newFunction1 = new randomFunction
newFunction2 = new randomFunction
currentFunction.setChildren(newFunction1,
newFunction2)
FuncQ.add(newFunction1)
FuncQ.add(newFunction2)
}
else if (currentFunction
is a Single Child Function){
newFunction1 = new randomFunction currentFunction.setChild
(newFunction1)
FuncQ.add(newFunction1)
}
}
Images
Images
are represented as BufferedImages. The BufferedImage
will be written a column of pixels at a time (using the setRGB() method) from a
thread which will be created to handle evaluation of the image function. This will allow for multiple images to be
evaluated concurrently.
It should
be noted that the TilingExternalImage and ClippingExternalImage states both scale the image they load
into the proper (-1, 1) range, basically meaning they are a StretchExternalImage. We found that this implementation was more
natural in our setup, and using Tiling and Clipping would have required
arbitrary constraints.
Getting images from functions
To get an
image from a function, the function is evaluated for each (X,Y)
pixel coordinate of the image, translated to the [-1,1] range. The resulting
RGB vectors are scaled to the [0,255] range and converted to RGB int values that can be fed into a BufferedImage.
A reference to the BufferedImage is passed to the
View right after the thread responsible for calculating the pixel values of the
image is started, so unevaluated pixels will initially be displayed as black.
As thread evaluates each column of pixels, the column of black pixels will be
replaced with the newly calculated values.
It should
be noted that in our version, X and Y state functions hold onto a local
constant they use to modify their return value.
This ensures that the images returned have more color variance than
would normally be the case. We found
(through experimentation) that this made for Prettier Pictures™.
Concurrent image evaluation
Threads
are used to calculate and display each image simultaneously in real time. Each
thread will first tell the view that it is calculating an image so that the
“Busy” icon will be displayed and then add itself to a list of running threads
maintained by the model. It will then get a random function tree via the method
described above and loop through all pixels of the image evaluating the
function for those coordinates (which it will first translate to the [-1,1]
scale). It will scale the RGB vector returned by the function for each pixel to
the [-1,1] scale and then convert it to an int format usable by the BufferedImage.
The thread will write each of these RGB int values to
an array until it reaches the end of a column of pixels. After each column, it
will write the entire array of RGB int values to the BufferedImage so that the image will be updated one column
at a time. The thread will then check to see if the “Stop” button has been
pressed, and if so, it will terminate and remove itself from the list of
working threads. Otherwise, the thread will traverse and calculate the entire
image before doing this.
The BufferedImage evaluated by the thread is actually passed to
the model before it is evaluated, but the thread retains a reference to it and
writes in the RGB values as they are calculated.
Threads
are controlled through synchronization around a set of currently running Thread
IDs. Threads place themselves into this
to indicate the model is busy; the last thread to leave this (runningThreads.isEmpty()) will indicate to the view that the model is now finished
computing.
The
threads all modify independent images and do not access shared variables, so
most work can be done independently.
However, when updating the view, Swing’s threading policy requires that
the Event-dispatching thread be the only thread allowed to modify swing
components. This means the use of the SwingUtilities.invokeLater method was required.
To stop
the threads from working, they all loop around the same Boolean condition. If this is set to true, all the threads will
begin removing themselves from the running threads set. The last thread out the door will reset the stop
condition to false.
Function/image
breeding
When
breeding two functions, pieces of a function tree may be randomly reassigned or
swapped with other functions, depending on the level of similarity of the
functions. If the two functions to be bred have head nodes and first-level
child nodes with the same states, the program will traverse both trees until it
encounters a node where the trees are different. This node in the child tree
will be randomly chosen from one of the two corresponding nodes in the parent
trees.
If the
two breeding trees do not have the same head node, a random node in one tree
will be selected and replaced by a random node from the other tree.
Two
utility functions are helpful in this process: getRandomNode(IFunction tree) and randomizeDifferentNodes(IFunction func1, IFunction
func2).
getRandomNode(IFunction tree) picks a random number less
than the number of nodes in the tree and traverses the tree, incrementing a
counter at each node. When it reaches the node at which counter is the same as
the pre-selected random number, it returns that node.
Pseudocode for getRandomNode(IFunction tree):
int size = number of nodes in tree
//nodeIndex is the index of the node that will be returned
int nodeIndex
= random int between 0 and size
//counter
keeps track of which node we are processing
int counter = 0;
//create
a new queue and add the root node to it
functionQ = new queue of function nodes;
functionQ.add(tree);
//while
the queue isn't empty, traverse the tree adding child
nodes to //the queue. each time a node is
//evaluated,
increment the counter. If the counter is the same as the //randomly selected nodeIndex,
//we have reached the node that we want to return.
While(the
queue isn’t empty){
//grab the top function from the queue
Function = queue.poll();
//increment the counter
counter++;
//check to see if we’ve reached the node
we want to return
if(counter
== nodeIndex){
return the
function.
}
//otherwise add the kids of this
function, if it has any
else {
queue.add(function’s
children);
}
}//close
while
RandomizeDifferentNodes(IFunction func1, IFunction
func2) takes two functions which should be similar (i.e., have head nodes with
the same state) and traverses them both. It creates a new “offspring” function
that is a clone of one of the parents. When it finds corresponding nodes between
the two functions that are unequal, it replaces that node in the offspring
function with the corresponding node from one of the parents at random. This
function performs most of the “breeding” for similar functions.
Pseudocode for randomizeDifferentNodes(IFunction func1, IFunction func2):
//make
queues for both functions, and the parent nodes.
Queues
func1Q, func2Q, parentsQ
//clone
the original functions so that the manipulations we’re
//about
to perform don’t mess them up
IFunction
newGen = clone(func1)
IFunction
oldGen = clone(func2)
//add the first nodes to the queues.
newQ.add(newGen);
oldQ.add(oldGen);
//add a
dummy function to the parents queue
parentsQ.add(dummy function with no state)
//while
the queues aren't empty
While(newQ and oldQ aren’t empty){
//pop the first node in each queue
IFunction newFunc = newQ.poll();
IFunction oldFunc = oldQ.poll();
IFunction parent
= newQparents.poll();
//if the two nodes we’re looking at aren’t
the same,
//i.e., they don’t have the same state
If(newFunc and oldFunc aren’t the
same){
//replace this new node with this old node with 50% //probability
If (a random number is > 50){
Parent.setChild(clone(oldFunc))
}
}
}
//return
the newGeneration.
return newGen;
When two
images are bred, there will be a small chance for mutation. Mutation will be
accomplished by selecting a random node from the tree and randomly reassigning
its state. For instance, a ZeroChildFunction with ConstantState could become a ZeroChildFunction
with XState, or a SingleChildFunction
with NegateState could become a SingleChildFunction
with RoundUpState. This is easily accomplished via
the getRandomNode function.
Animation
When
animating between two images, a genetic cross dissolve technique is used. The trees
are traversed recursively, and when dissimilar nodes are found, they are
interpolated between for the user-selected number of frames.
To
generate the set of images displayed by the View, the Model uses a technique
very similar to the normal image display method. A list of images is created
with length equal to the user-specified number of frames in the animation, and
for each image in the list, a thread is created that will calculate the RGB
values for each pixel in the image. The main difference from the typical image
loading technique is that instead of directly evaluating the functions, this
thread uses the interpolateFunctions method.
InterpolateFunctions(func1, func2, int
x, int y, int frame, int totalFrames) is used to
interpolate between functions. Func1 and 2 are the functions, x and y are the
pixel coordinates (scaled to [-1, 1]), frame is the current frame number of the
animation, and totalFrames is the number of total
frames in the entire animation. The method works by recursively evaluating each
node both functions. If the corresponding nodes from the functions are equal
(i.e., have the same state), the value of a recursive call of interpolateFunctions() on the children of those nodes is returned if they have
children, and if they do not have children, their value is returned. If the
corresponding nodes from the functions are different (have different states),
both function nodes are evaluated and an intermediate value is returned based
on the position of the “frame” input relative to the number of total frames.
The returned value will be
(func1.returnValue * (frame/totalFrames)) + (func2.returnValue * ((totalFrames – 1 – frame) / (totalFrames
- 1)))
Where the
“-1”’s are present to account for the fact that “frame” begins counting at 0. The
parent nodes of these interpolated nodes will operate on the results of the
interpolation rather than their actual child node outputs; this is accomplished
using the overloaded version of returnValue in IFunction that takes in vectors and operates on these
vectors, treating them as the output of child nodes. The interpolated values
are passed in, and the actual child nodes remain unevaluated. In this way many
interpolations between 2 functions can be produced without actually modifying
either function.
Saving/loading
Users
will have the option of saving images to PNG format or PRP format. The PRP format is just a String
representation of a function saved with a .prp
extension. If the user tries to load a
.PRP file, it will parse the function and create the image. If the image cannot be created, the exception
will be caught and the user notified. When
the user loads a PNG file from their system, the program will construct a ZeroChildFunction that uses the loaded image to translate
XY pixel coordinates into RGB values. This function can then be used to
display, breed, and animate the image.
Animations
can be saved as a series of PNG files. The number of files will correspond to
the user-specified number of frames in the animation.
Classes
Controller
PrettyPictures.java
This is
the controller class for PrettyPictures, containing
the main method (which takes no arguments).
It creates the View and Model instances and initializes both, creating
anonymous inner class adapters for communication between them.
Model
Model.java : This class provides all major
functionality of the program. It handles history tracking, image caching, image
generation, function breeding, function animation, random function generation, and
image saving and loading.
Function
breeding, animation, saving/loading, and random generation are all discussed
above in the Function/image breeding, Animation, Saving/loading, and Generating random
functions and function trees, respectively.
The model uses a method called getImage(IFunction
func, int width, int height) to get an image of the specified dimensions
from a function. This is used whenever an image is needed, except in animation
when interpolation is necessary (as discussed in the Animation section
above). When a user requests a zoomed version of an image, this method is used
to produce it – the width and height inputs will simply be larger than those of
a thumbnail.
Since the View doesn’t have access to formulas, the
arguments to the breeding and animation methods will be BufferedImages.
Therefore the Model needs to be able to look up a function given the image that
resulted from that function. This ability is provided by a hashmap<BufferedImage, IFunction>
stored in the model.
The user has the ability to modify the red, green,
and blue saturation of displayed images via the use of sliders in the GUI.
These modifications do not affect the underlying functions of the images,
rather, the sliders control float values that are simply added into the RGB
vector results of function evaluation for each pixel in an image. The model
keeps track of the current positions of the sliders in order to correctly set
the saturation levels for each generated image.
The model maintains a list of all running threads.
New threads are generated whenever an image is created from a formula. When a
user stops the current generation while it is loading, the view calls the stop() method in the model. This method sets a flag called
_stop to true. All threads check this flag at each iteration,
so once it is set, the threads will cease whatever they were doing, remove
themselves from the list of running threads, and tell the view adapter that the
view is no longer busy.
IModel2View.java
This
class is the Model 2 View adapter that allows communication between model and
view. It is created anonymously in the PrettyPictures class.
IFunction.java : IFunctions
represent compound functions in a binary tree format. A general explanation can
be found in the Functions and function
representation section above.
DualChildFunction.java : DualChildFunctions represent
functions with two child nodes (arguments), such as addition, subtraction,
multiplication, and division.
IDualChildState.java :
A state of a dual child function. This state will represent one of the dual
child operations listed above.
SingleChildFunction.java : SingleChildFunctions represent
functions with one child node (argument), such as negation, rounding up, color
space conversion, etc.
ISingleChildState.java :
A state of a single child function. This state will represent a single argument
operation.
ZeroChildFunction.java : ZeroChildFunctions represent
functions with no child nodes (arguments), such as constants, X, Y, and
external images.
IZeroChildState.java :
A state of a zero child function. This state will simply return a numeric
value, whether it be a constant vector, a scaled vector based on the X or Y
arguments, or a vector based on the RGB value of the (X,Y) pixel of an external
image.
Utilities.java : The Utilities class contains
useful static methods that are used throughout the Model class, as well as
helper methods for breeding and animation. In particular, it contains the
methods randomizeDifferentNodes and getRandomNode (which are discussed in the Breeding section above), as well as a
method that finds the number of nodes in a function tree by the following
method:
Pseudocode for getFunctionSize(IFunction function):
int counter = 1;
for all children of function{
counter += getFunctionSize(child function);
}
return counter;
Utilities contains numerous methods for scaling and manipulating function
input and output. These include scaling x,y
pairs to the [-1,1] range, scaling 3-dimensional vectors to the [-1,1] range,
scaling 3-dimensional vectors in the [-1,1] range to the [0,255] range (and
vice versa), and translating 3-dimensional RGB value vectors into RGB ints usable by the BufferedImage
class (and vice versa).
Utilities
contains a method loadFunction(String state) that
uses dynamic class loading to create a new instance of a function with a given
state, which is passed to the method as a String describing the state’s path
(such as “model.AddState”). This is used in creating
random generations (as discussed in the Generating
random functions and function trees section) as well as in function
cloning.
Utilities contains methods that are used to “clone” functions, creating
independent copies of existing functions. These are used in the breeding
process to create copies of parent functions that can be modified without
mutating the original parent. The two methods that are used in this process are
copyFunctionNode(IFunction func),
which creates a cloned copy of a single function node (disregarding its child
nodes), and cloneFunction(IFunction
func) which clones an entire function, children and
all.
CopyFunctionNode uses the loadFunction method
described above. It pulls the “path” String from the state of the input
function and uses it to create a new function node with the same state. It then
sets any relevant fields in the new state and returns the node.
CloneFunction uses recursive calls to copyFunctionNode in
order to copy an entire function.
Pseudocode for cloneFunction(IFunction func):
IFunction
result = copyFunctionNode(func);
If(func has
children){
Result.setChildren(cloneFunction(func’s
children));
}
return result;
AreSimilar(IFunction func1, IFunction
func2) that determines if two functions are similar by comparing the states of
their head nodes and first-level child nodes. If these states are the same for
both functions, then they are similar and have the same genotype for breeding
purposes.
Utilities also contains methods for generating random numbers, which are used
throughout the model.
View
View.java
The view
is a fairly standard swing implementation of a GUI. It handles threading through the use of SwingUtilities.invokeLater(), which hands a runnable
instance to the Event-Dispatching-Thread, which is the only thread allowed to
modify swing components.
The
choice of a black GUI was purely a programmer’s aesthetical choice. Had we a bit more time, a color choice could
have been implemented that would allow user-end selection of the background
color. After all, we aren’t designing
user-end commercial database software. It
should look different™.
IView2Model.java
This
class is the View 2 Model adapter that will allow communication from View to Model. It is created anonymously in the PrettyPictures class.
Analysis
Evaluating an image function to
display the image consists of a tree traversal of x nodes and returning n
pixels, so it should be O(n log x).
Breeding two images should be O(log n) for two similar trees with n nodes. Breeding
dissimilar images is O(1) because a node is randomly
selected and replaced.
Animating two images is O(log n), as it is comparable to displaying an image
repeatedly for a set number of frames.
Testing
Unit
Tests
·
Unit tests will be implemented for all public methods of
the ‘model’ package. Tests will be implemented
in the ‘test’ package, class ‘AllTests’.
Functional
Tests
·
Functional tests will be performed for all ‘view’
components. Examples include:
o
Ensure concurrent loading of images.
o
Ensure button press combinations do not cause deadlock or
exceptions to be thrown.
o
Animate between two images and make sure that the
animation displays correctly.
o
Check that all UI buttons perform their assigned actions.
·
Breeding will be tested functionally by breeding images
and printing out the resulting function, making sure that it was correctly
generated from its parent functions.
Time
Table
|
Task |
Estimate |
Order |
Completed |
Actual |
|
Controller |
|
|
|
|
|
PrettyPictures |
1 hr |
1, jcr2 |
4/2, jcr2 |
1 hr |
|
View2Model |
1 hr |
1, jcr2 |
4/6 jcr2 |
1 hr |
|
Model2View |
1 hr |
1, jcr2 |
4/6, jcr2 |
1 hr |
|
Model |
|
|
|
|
|
Defining Functions |
4 hr |
1, jtb5020 |
4/2, jtb5020 |
2 hr |
|
Generating Images from Functions |
5 hr |
3, jtb5020 |
4/14, jtb5020, jcr2 |
4 hr |
|
Generating Random Functions |
3 hr |
3.a, jtb5020/jcr2 |
4/14, jtb5020/jcr2 |
4 hr |
|
Breeding Image Functions |
6 hr |
4, jtb5020 |
4/21, jtb5020 |
8 hr |
|
Parsing User Function Input (?) |
5 hr |
7, jtb5020 |
4/22, jcr2 |
4 hr |
|
Generating Animations |
5 hr |
5, jtb5020 |
4/21, jtb5020 |
2 hr |
|
Threading Processes |
6 hr |
2, jtb5020 |
4/10, jtb5020 |
2 hr |
|
Image Input/Output |
2 hr |
6, jtb5020 |
4/17, jcr2 |
1 hr |
|
View |
|
|
|
|
|
UI Layout |
5 hr |
2, jcr2 |
4/2, jcr2 |
4 hr |
|
Back/Forward |
2 hr |
7, jcr2 |
4/17, jcr2 |
2 hr |
|
Stop/Refresh |
1 hr |
6, jcr2 |
4/17, jcr2 |
3 hr |
|
History |
2 hr |
5, jcr2 |
4/17, jcr2 |
4 hr |
|
Loading Images |
3 hr |
3, jcr2 |
4/8, jcr2 |
3 hr |
|
Loading Concurrently |
2 hr |
4, jcr2 |
4/10, jcr2 |
2 hr |
|
Loading Animations |
4 hr |
9, jcr2 |
- |
- |
|
Zoom Page |
1 hr |
2, jcr2 |
4/10, jcr2 |
2 hr |
|
Accept User Input |
2 hr |
8, jcr2 |
|
|
|
Manipulating Selected Images |
2 hr |
8.a, jcr2 |
4/17, jcr2 |
2 hr |
|
Opening Files |
3 hr |
8.b, jcr2 |
4/17, jcr2 |
2 hr |
|
Mutation/Alteration Input |
2 hr |
8.c, jcr2 |
4/21, jcr2 |
2 hr |
|
Input Function Changes |
1 hr |
8.d, jcr2 |
4/22, jcr2 |
1 hr |
|
Allow Saving/ Renaming Pages |
1 hr |
8.e, jcr2 |
4/20, jcr2 |
1 hr |
|
Finding Pretty Icons |
1 hr |
1, jcr2 |
4/2, jcr2 |
1 hr |
|
|
|
|
|
|
|
Testing
and Debugging |
|
|
|
|
|
Sanitizing View Input Options |
5 hr |
10, jcr2 |
4/22, jcr2/jtb5020 |
6 hr |
|
Spec Update and Javadoc |
3 hr |
9, jtb5020 / 11, jcr2 |
4/22, jcr2/jtb5020 |
4 hr |
|
|
|
|
|
|
|
Total |
71 hr |
|
|
69 hr |
Function Samples:
AbsVal(TilingExternalImage)

ATan(TilingExternalImage)

Add(TilingExternalImage, X(Constant[-1.7358335r -1.7285873g
0.61650896b]))

Clip(TilingExternalImage)

ColorPerlinNoise(TilingExternalImage)

Constant[-0.21378076r -1.7041718g 0.46798754b]

Cos(TilingExternalImage)

Divide(TilingExternalImage, X(Constant[-1.7358335r -1.7285873g
0.61650896b]))

GrayScalePerlinNoise(TilingExternalImage)

LC_To_RGB(TilingExternalImage)

Log(TilingExternalImage)

Multiply(TilingExternalImage, X(Constant[-1.7358335r -1.7285873g
0.61650896b]))

Negate(TilingExternalImage)

Pow(TilingExternalImage, 2.0)

RGB_To_LC(TilingExternalImage)

Round_Down(TilingExternalImage)

Round_Up(TilingExternalImage)

Sin(TilingExternalImage)

Subtract(TilingExternalImage, X(Constant[-1.7358335r -1.7285873g
0.61650896b]))

TilingExternalImage

Wrap(TilingExternalImage)

X(Constant[-1.7358335r -1.7285873g 0.61650896b])

Y(Constant[-1.0471748r -1.0145581g -0.17606437b])

Cool Pictures








bravenet.com