Author Archives: ccpalettes

About ccpalettes

A simple web developer, enjoy creative code and art.

Convert an image to indexed color mode with PHP

Indexed color is a technique to display an image with only a few specific colors. With indexed color mode, you can reduce the image file size or achieve amazing image effects.

In the GD library of php, there is a function called imagetruecolortopalette that could converts a truecolor image to a palette image. But using this function, you can’t decide which colors that the output image will contain, instead, you can only set the maximum number of colors that will be retained in the output image. In addition, there is no dither amount control in the this function.

I write the GDIndexedColorConverter library with PHP to achieve advanced indexed color conversion. To archieve image dithering effect, GDIndexedColorConverter uses Floyd–Steinberg dithering algorithm to apply error diffusion of each pixel onto its neighboring pixels.

Usage

GDIndexedColorConverter uses some functions of the GD extension, you need to enable GD extension in the PHP configuration file (php.ini).

GDIndexedColorConverter provide a function named convertToIndexedColor to convert an image into indexed color mode, it accepts three parameters(listed below), and return a new image resource of indexed color mode.

  • im (imageresource) The image resource created by the functions of GD library.
  • palette (array) The palette which contains all the specific colors that the indexed-color-mode image will use. This parameter is an array which stores all the colors, each color is an indexed array that consists of red, green and blue color channel values.
  • dither (float) How much the Floyd–Steinberg dithering algorithm will affect the image. This parameter is optional, its default value is 0.75, and the value must be between 0 and 1.

Code example:

// create an image
$image = imagecreatefromjpeg('example.jpg');

// create a gd indexed color converter
$converter = new GDIndexedColorConverter();

// the color palette
$palette = array(
    array(0, 0, 0),
    array(255, 255, 255),
    array(255, 0, 0),
    array(0, 255, 0),
    array(0, 0, 255)
);

// convert the image to indexed color mode
$new_image = $converter->convertToIndexedColor($image, $palette, 0.8);

// save the new image
imagepng($new_image, 'example_indexed_color.png', 0);

Dithers

Applying different dither values on indexed-color images, you can get various image effects. For instance, the picture below shows three output images with different dither values(0.2, 0.4, 0.8) and five colors(white, black, red, green and blue). The original image that I use in this example is created by @sage_solar, the image is under Creative Commons License.

example_output

To check more information about GDIndexedColorConverter, visit https://github.com/ccpalettes/gd-indexed-color-converter

Less CSS with Breezeless

Breezeless is a library that provides a set of basic or advanced mixins for LESS(a CSS pre-processor).

Features

Breezeless focus on providing reusable LESS mixins and resolving complicated headaches in CSS coding.

  • Easy to use. All the mixins in Breezeless start with bl- prefix, and keep their names the same as corresponding CSS properties as much as possible.
  • No CSS output. All the mixins don’t output any CSS, thus, you can use these mixins inside your own LESS mixins, but you can’t use the bl- prefixed names as CSS classes in HTML.
  • Cross-browser. Breezeless generates vendor-prefixed versions of a CSS property. Breezeless follows a rule to decide whether a vendor-prefixed CSS property should be added into a mixin. If the market share of any version of any kind of the browsers that need a vendor-prefix to support a specific CSS feature is equal to or greater than 0.02%, the vendor-prefix version CSS property will be used.(All the market share data is get from Can I use.)
  • Merged properties. Breezeless solves special cases when a standard CSS property is supported but another CSS property name that is used as a parameter of the former CSS property is maybe not supported(need add vendor-prefix) in a browser. For instance, Chrome 35 supports transition, but doesn’t support transform(need add -webkit- vendor-prefix), with prue CSS, you need write transition: -webkit-transform 2s; in Chrome 35 and write transition: transform 2s; in the latest version of Chrome. Breezeless solves this problem in one line .bl-transition(transform 2s);.
  • Helpes. Breezeless also provides some useful helpers to deal with common browser compatibility issues.

How to Use

  1. Download the breezeless.less file and place it in your working LESS folder.
  2. Reference breezeless.less in the top of your LESS files.
  3. @import (reference) "breezeless.less";

Documentation

  • border

Description

Set border style property of an element. A complete border style consists three properties, they are width, style and color.

Usage

• When the length of arguments is less than or equral to 3, same style will be applied to all four borders. You can pass border width, border style and border color into the arguments individually, or passed a single value list combined of them to the mixin. The values of width style and color are optional, the mixin will apply a default value if you leave any of them empty.

.bl-border(2px);
.bl-border(2px; #999);
.bl-border(0 dashed #A87);

• When the length of arguments is 4, the four arguments represent top border, right border, bottom border and left border respectively, each argument is a value list which may consists border width, border style and border color. If one argument’s value is set to null, then no style will be applied to the relevant border.

.bl-border(6px; 3px; 1px; 2px);
.bl-border(solid #FFF000; null; 3px dashed; 2px dotted #909090);

• Default values. By default, border width is set to 1px, border style is set to solid, and border color is set to #000.

// equivalent to .bl-border(6px solid #000; 1px solid #FF0; 1px dashed #000; 1px solid blue);
.bl-border(6px; #FF0; dashed; blue);
  • inline-block-fix

Description

The inline-block display fix. Fix to support inline-block display for IE6 and IE7.

Usage

.bl-inline-block-fix();
  • box-sizing

Description

Mixin for box-sizing CSS property.

Usage

.bl-box-sizing();  // default value: border-box
.bl-box-sizing(border-box);
  • user-select

Description

Mixin for user-select CSS property.

Usage

.bl-user-select();  // default value: none
.bl-user-select(text);
  • clearfix

Description

CLear an element itself which contains floated children.

Usage

.bl-clearfix();
  • box-shadow

Description

Set single or multiple shadows to one element.

Usage

//single shadow
.bl-box-shadow(3px 3px 6px 2px rgba(0, 0, 0, 0.2));

//multiple shadows
.bl-box-shadow(1px 1px 2px 0 #F00; inset -1px -1px 2px 0 #00F);
  • border-radius

Description

Set border-radius property of an element.

Usage

.bl-border-radius(e("60px 20px / 30px"));
.bl-border-radius(10px);
  • opacity

Description

Set opacity of an element, support IE6 and above.

Usage

• You can pass a number to the opacity argument.

.bl-opacity(0.5);

• A percentage number is also valid.

.bl-opacity(30%);
  • transition

Description

Set single or multiple transitions to one element.

Usage

• BreezeLess defined five transition mixins, one mixin is used to set the shorthand transition property, and the other four are used to set specific transition CSS property. Each mixin accept one or more arguments.

• When you need to pass “transform” as the transition-property value, you don’t need to add vendor-prefixes before “transform”, BreezeLess will do it for you.

• bl-transition. Set the shorthand transition property.

.bl-transition(transform 1s);
.bl-transition(width 2s; height 2s);

• bl-transition-property. Set transition-property property.

.bl-transition-property(transform; color);

• bl-transition-duration. Set transition-duration property.

.bl-transition-duration(1s; 2s);

• bl-transition-timing-function. Set transition-timing-function property.

.bl-transition-timing-function(ease);

• bl-transition-delay. Set transition-delay property.

.bl-transition-delay(1s);
  • animation

Description

Set single or multiple animations to one element.

Usage

• BreezeLess defined nine transition mixins, one mixin is used to set the shorthand animation property, and the other eight mixins are used to set specific animation CSS property. Each mixin accept one or more arguments.

• bl-animation. Set the shorthand animation property.

.bl-animation(swing 3s ease 1s 2 alternate both running);
.bl-animation(bounce 5s; rotate 2s);

• bl-animation-name. Set animation-name property.

.bl-animation-name(swing);

• bl-animation-duration. Set animation-duration property.

.bl-animation-duration(3s; 5s);

• bl-animation-timing-function. Set animation-timing-function property.

.bl-animation-timing-function(ease-in; ease-out);

• bl-animation-delay. Set animation-delay property.

.bl-animation-delay(0s; 3s);

• bl-animation-iteration-count. Set animation-iteration-count property.

.bl-animation-iteration-count(2; infinite);

• bl-animation-direction. Set animation-direction property.

.bl-animation-direction(alternate; reverse; alternate-reverse);

• bl-animation-fill-mode. Set animation-fill-mode property.

.bl-animation-fill-mode(none);

• bl-animation-play-state. Set animation-play-state property.

.bl-animation-play-state(paused; running);
  • ellipsis

Description

Display an ellipsis (‘…’, U+2026 HORIZONTAL ELLIPSIS) for a clipped text.

Usage

.bl-ellipsis();
  • transform

Description

Set transform related CSS properties.

Usage

• bl-transform. Set the shorthand transform property.

.bl-transform(rotateX(60deg));

• bl-transform-origin. Set transform-origin property.

.bl-transform-origin(50px 100px);

• bl-transform-style. Set transform-style property.

.bl-transform-style(preserve-3d);

• bl-perspective. Set perspective property.

.bl-perspective(20px);

• bl-perspective-origin. Set perspective-origin property.

.bl-perspective-origin(0 50%);

•  bl-backface-visibility. Set backface-visibility property.

.bl-backface-visibility(hidden);

More

To see more examples and references, you can check the breezeless source code.

Remote Debugging PHP with VIM and XDebug

Many php developers enjoy outputting debugging information using functions such as var_dump during the development process, but sometimes it’s not effective if the program logic is complex or if you want to step through code line by line. To console debugging information, we have to manually add debug statements in our code, this is really a waste of time. Fortunately, there is a useful php extension called XDebug which provides debugging, profiling, tracing and code coverage support. With the debugging ability of XDebug, we could add breakpoints, watch variables and examine the call stack in our php code, just like IDEs for other languages such as java.

Now I have a local machine (debugger client) that runs Mac OS X 10.8 and a virtual machine (php + web server) that is running Ubuntu Server 12.04 operating system as a remote server in Oracle VM VirtualBox. In the virtual machine, there is a php web application. In this article, you’ll see how to install and configure XDebug, and how to debug the php application using VIM on a local machine.

The picture below describes the theory of remote debugging with xdebug.

theory about remote debuging with xdebug

In order that the local machine and remote server could access each other, I create a host-only network with virtualbox, the local machine could visit the site on the remote server through address http://192.168.57.101, and the remote server could access the local machine through ip 10.0.2.2. About setting host-only network in virtualbox, you can visit this article Host-Only Networking With VirtualBox.

Install XDebug in Remote Server

  • First download xdebug source files, then compile it to .so extension. Before compiling, make sure you have installed php5-dev package, if not, Debian users can install it by typing command:
    $ sudo install php5-dev
  • Compile xdebug.
    # run phpize, use phpize in your own environment instead.
    $ /usr/bin/phpize
    
    # remember use the path of php-config in your own environment.
    $ ./configure --enable-xdebug –with-php-config=/usr/bin/php-config
    make
    
    # copy xdebug.so to php extensions directory
    $ sudo cp modules/xdebug.so /usr/lib/php5/20090626/
  • Edit the php configuration file php.ini, it’s located at /etc/php5/cli in my remote server. Then add these lines to php.ini.
  • [xdebug]
    zend_extension=xdebug.so
    xdebug.remote_enable=1
    ; The port to which xdebug tries to connect on the remote host.
    xdebug.remote_port=9000
    ; The host where the debugger client is running, 10.0.2.2 is the ip address
    ; of the debugger client machine.
    xdebug.remote_host=10.0.2.2
  • Restart the web server. To check whether xdebug extension takes effect, you could create a phpinfo page, and see whether the xdebug extension information appears on the page result.

    xdebug infomation in phpinfo page

Configure VDebug in VIM

In the local machine (debugger client), I will add a plugin called vdebug to the vim editor. Vdebug is a new, fast, powerful debugger client for Vim, it interfaces with any debugger that faithfully uses the DBGP protocol, including xdebug for PHP. One important issue to note is that the vim must have tabs, signs, python support and the version of python should be 2.6+.

  • Download vdebug and uncompress the pachage, then copy its content to your ~/.vim/ directory.

    For more installation methods about vdebug, please refer to README of vdebug.

  • Configure options for vdebug. You need set the port to which the xdebug connects, edit file ~/.vimrc, add these lines:
    let g:vdebug_options = {}
    let g:vdebug_options["port"] = 9000
  • Map code paths. To use xdebug remote debugging, a same copy of the php files in the remote server should already exist in the local machine. Although the php application runs in the remote server, but if you want to debug php files in local vim editor, the vim needs display the sames files for you. For example, the path of the php source files on my remote server is /var/www, and the I have the same code files in the directory /Users/ccpalettes/php/xdebug on my local machine. So I append the lines below to file ~/.vimrc.
  • let g:vdebug_options["path_maps"] = {
    \    "/var/www": "/Users/ccpalettes/php/xdebug"
    \}

How to Use

As I mentioned earlier, I could access the php web application through http://192.168.57.101 on my local machine. Follow these steps below, I could debug php files easily.

  • Open the php file that you want to debug in vim.
  • Put the cursor over the line to which you want to add a breakpoint, press F10, you can see the line will be highlighted which indicates a breakpoint has been set. To remove the breakpoint, just repeat the action on the same line.

    add a breakpoint

  • Then press F5 to start the debugger, a message will be shown at the bottom of the vim editor.debugger start message
  • Open your browser on the local machine, input the url of the web application (http://192.168.57.101) into the address bar and append ?XDEBUG_SESSION_START=1 to the end of the url. Finally, the url will be concatenated into http://192.168.57.101?XDEBUG_SESSION_START=1. Parameter XDEBUG_SESSION_START will be saved in the cookie in your browser which tells the xdebug to make a connection with the debugging client.
  • After refreshing the page, the xdebug should work now, and you will able to see source, watch, stack and status window in the vim.

    debugger windows

Here are a few available commands in vdebug. You can see more details in vdebug document.

Run               <F5>
Step over         <F2>
Step in           <F3>
Step out          <F4>
Run to cursor     <F9>
Detach            <F7>
Stop/close        <F6>

Debugging in local environment

Although I am talking about remote debugging php, but these steps also could be applied to local debugging. The only difference is that the remote server and the debugger client are actually the same machine. The image below show how the xdebug works under local environment.

remote debugging php in local environment

One more note

XDebug is powerful, it provide you not only the debugging ability, but also a lot of features waiting to be discovered by yourself.

LoremText plugin for Sublime Text

Sublime Text is a cross-platform text and source code editor. It attracted a lot of users since the stable version 2 had been released last year, and I really love it!

Sublime Text has an elegant user interface, it also provides a number of awesome functionalities, such as multiple selection, go to anything, file palette, vintage mode, etc.

We all know that every powerful editor has a strong plugin system, and Sublime Text is no exception. Sublime Text has a python based plugin system, If you have interests in writing a Sublime Text plugin, you can read this article How to Create a Sublime Text 2 Plugin .

There are already more than 1000 plugins in the Package Control plugin management system for Sublime Text. Of course, this one named “LoremText” which is written by myself is one of them. The source code for “LoremText” is maintained on github, you can download code from this page https://github.com/ccpalettes/sublime-lorem-text.

LoremText is a plugin for sublime text 2 that generates random or fixed lorem ipsum text. Lorem ipsum is commonly used as placeholder text before you have the real meaningful content. Think back, if you are a designer there must be a lot of times that you designed a page, but no one told you the real meaningful text to be filled into the content blocks. If you are a programmer, there must be also a lot of times that you need a large string, but you use repeated “abcdefg123456” instead. Ha, lay aside those bad habits from now on, cause lorem ipsum will help you.

Below is a sample text of lorem ipsum. Although, we all have no idea of the meaning of this paragraph, but you still can consider it as some kind of human language.

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

LoremText is exactly a plugin to create lorem ipsum pattern text. It has a word dictionary which contains more than two thousand words. After you installing it into your Sublime Text editor, you can execute it from command palette, edit menu, or just press the shortcut key alt+l. The GIF picture below shows how to use the LoremText plugin. 

LoremText Example

An advantage of LoremText is that it could create any number of words or paragraphs exactly you want. LoremText could recognize the lorem text expression which is defined by itself. The style of lorem text expression is like a function call, the pattern of the expression is lorem({word_count}, {paragraph_count}).  Parameter “word_count” represents how many paragraphs to be inserted and parameter “paragraph_count” represents how many words that each paragraph contains. For example, lorem(100, 3) means that it will create three paragraphs of lorem ipsum text and each paragraph contains one hundred words. Both the two parameters are optional,if you leave them empty, the plugin will use default values that are configured in the settings file. So lorem(,5) will use default word count for five paragraphs in the settings, lorem() will use default values for both parameters, actually, if you want to use default values, you don’t need to input lorem(), just press “alt+l“, lorem ipsum text will be inserted after the mouse cursor automatically.

With the help of multiple selection feature in Sublime Text, you can insert multiple lorem ipsum blocks at the same time. but keep in mind, the mouse cursor must be within the region of the lorem text expressions.

How to Make a Push Pin Art in An Arrangement of Equilateral Triangles

The pictures below show the original image and the final result of the push pin work, it looks cool, doesn’t it? By using push pins in only a few colors, my colleagues in Esri(Beijing) R&D Center and I created this push pin work, it’s really interesting!

original and final work

perspective view

This article talks about how to convert an image into a push pin image, and I’ll assume that you are familiar with Adobe Photoshop. OK, let’s get started!

Step 1. Select your image.

Select an image which you want to use for your push pin work, the entire work will be based on this image. Then open this image in Adobe Photoshop, you should be able to see it in the Layers panel.

Layers Panel

Step 2. Resize the image.

The original image which I choose is 5100 * 3000 pixels in size, it’s too large because each pixel represents a push pin, I will adjust the image to a smaller size, for me, 510 * 300 is a good choice. In adobe photoshop, you can select menu Image -> Image Size… to resize an image. But if you insist on using large images, you will need lots of time and a wall which is big enough for all the push pins.

Resize Image

Step 3. Transform the image.

Actually, make a push pin image in a rectangular arrangement is easier than in an equilateral triangular arrangement, but you may notice that if we arrange push pins in equilateral triangles, we could make the push pins arranged more closely and increase space utilization, of course, the effect will also be better. In my view, it is quite a valuable part during the whole making process.

At the moment, the image cannot be transformed yet, so what we need to do first is unlock the image layer(also called making layer) in Layers panel. Once the layer is unlocked, we could apply two transformations to the image. To transform the image, select menu Edit -> Free Transform (shortcut key is Command + T), set the value of H(horizontal skew) to -30 degrees, then press enter to apply the transformation.

transformation skew horizontal -30

Next, the image need another horizontal scale transformation,  select menu Edit -> Free Transform again, set the value of W(horizontal scale) to 86.60254 percents and do not check maintain aspect ratio option, then apply the transformation.

transformation scale width 86.60254

If you are interested in how the number 86.60254 come out, please read this section, if not, just ignore it.
Because all angles in an equilateral triangle are equal to 60 degrees, that's why we skew the image by -30 degrees horizontally, after skewing, the square shape becomes a parallelogram which has two angles equal to 60 degrees and two angles equal to 120 degrees.
change square shapeHere comes the secret, 0.8660254(86.60254 percents) is equal to the value of sine 60 degrees, and you will see number 115.47005 percents(1.1547005) later, it's the inverse of 0.8660254.

Do make sure that you do these two transformations separately, otherwise, you will not be able to recover the distorted image later. The following image shows the effect after the two transformations.

after first two transformations

Step 4. Enlarge canvas size.

After completing the previous step, part of the image is beyond the boundary of the canvas. In order not to affect the follow-up actions, we should enlarge the size of the canvas. Select menu Image -> Canvas Size…, set the value of Width to 125 percents, then press OK.

enlarge canvas

Now you could see the hidden part of the image.

after enlarge canvas

Step 5. Set image to indexed color mode.

Indexed color use limited colors to display an image. Now I have push pins in six different colors, these colors are  red(#FF0000), green(#119911), blue(#0088DD), yellow(#FFEE00)white(#FFFFFF) and black(#000000). I will use these six colors to index my image.

Push Pin

In photoshop, select menu Image -> Mode -> Indexed Color….

Indexed Color

In the opened dialog, set the value of Colors to the count of the colors you have, for me, the value is 6 because I have push pins in six colors. Next, you need to specify these colors, choose Custom… in the Forced drop-down list, then another dialog will open, add all the six colors to the palette, click OK.

Forced Colors

Here is the look of the image in indexed color mode, you can see each pixel has been converted into one of those six colors.

indexed color mode image

Step 6. Enlarge each pixel.

Now each pixel in our image has a corresponding color, but when you want to print this image, you will realize that the space for a push pin marker is too narrow, so it’s better to enlarge all the pixels. Another advantage of this step is that later you can replace each push pin marker with a customized pattern which could helps you find the center points of the push pin markers area much easier. To enlarge pixels, select menu Image -> Image Size…, I set 3200 percents for both width and height in Pixel Dimensions options, this will make each push pin marker area 32 * 32 pixels in size.

Enlarge Pixels

Now you could see that each pixel is magnified by 32 times.

Enlarged Pixels 32 Times

Step 7.  Transform the image back.

In step 3, we have transformed the image twice, now we need to recover the distorted image. But images in indexed color mode cannot be transformed, so we need to change the image back into RGB mode and unlock the layer again. To recover image, the image need another two transformations, and these two should also be applied separately. The first is to scale the width of the image, set the horizontal scale to 115.47005 percents. And the second one is a skew transformation, set the horizontal skew to 30 degrees.

Zoom in the image, you can see the shapes for push pin markers become into parallelograms.

diamond shape push pin area

Actually, now you can print this image and start pushing push pins on the wall. Although the push pin marker areas are already in an equilateral triangular arrangement, but these markers are still not clear enough, especially when marker areas in the same color are connected, and it’s difficult to find centers of these areas. To improve the usability of the push pin image, we could replace these areas with customized patterns, I’ll talk about this in the next few steps.

Step 8. Create patterns.

The purpose of creating patterns is to make the push pin markers more clearly, we could make patterns into circular shapes with obvious central markers. Since each push pin marker area is in 32 * 32 pixels, the patterns should be in the same size. I have push pins in six colors, it means that I will create six different patterns.

These are the six patterns created by me.

before transformation

six patterns

after transformation

six distorted patterns

You can download the pattern psd file from this link: https://www.dropbox.com/s/vemot5gshvie6n7/patterns.psd

You can also download the adobe photoshop pattern format(.pat) file from this link: https://www.dropbox.com/s/3x0tqb6t1g9vdl1/push%20pin%20patterns.pat

Step 9. Create new layers filled up with the new patterns.

I will take the red pattern as an example in this step. Go back to the push pin image psd file, add a new layer in Layers panel, then select menu Edit -> Fill…, in the opened dialog, use Pattern as the fill source and choose the red pattern that you created in step 8, click OK.

fill pattern

The new layer will be filled with the red pattern.

filled with red patern

But for now, the pattern layer is not arranged in equilateral triangles, we need do two more transformations! Gosh, how many transformations have we applied? These two transformations are same as the two in step 7. First transformation is to scale the width of the layer, set horizontal scale to 115.47005 percents, second transformation is to skew the layer, set horizontal skew to 30 degrees. Now the patterns should be in the arrangement as we expect.

patterns arranged as expected

Maybe the pattern layer is not aligned to the push pin image layer right now, then you just need to move the pattern layer a few pixels in horizontal and vertical directions, make sure that the center of a pattern shape and the center of its related push pin marker are at the same point. The image below shows the result.

align patterns layer

Step 10. Replace push pin markers with patterns.

To replace the red push pin marker areas, we need find those red marker areas. Select Magic Wand tool, set Tolerance to 50 and do not check Contiguous option. Then select the push pin layer in Layers panel, click mouse on any red push pin marker area, it will generate a selection which contains all the red push pin marker areas for you. After this, click Add layer mask button to add a mask on the red pattern layer.

add layer mask

You can find that all the red pattern shapes have disappear except those which are within the red push pin marker areas.

red patterns within red push pin marker areas

Step 11. Process the rest patterns.

Repeat step 9 and step 10 for the rest color patterns. Once all the pattern layers have been done, the result would be like the following image.

six patterns layers

Almost done, but there is still a little problem: there are useless space on left and right sides because we have enlarged canvas size in step 4, we need to make sure that the canvas is the same size as the push pin image. Select menu Image -> Canvas Size…, set the value of Width to 80 percents, then press OK.

reduce canvas size

After reducing the size of the canvas, we get the final effect at last.

final effect

Step 12. Push the push pins!

Congratulations! Now you have the push pin image, print it out and put it on a wall, and start pushing! Be sure to get enough rest. Here is the final work done by my colleagues in the office.

final work

************************************

If you are not familiar with Adobe Photoshop, or you are tired of completing the whole process by yourself, I can create the push pin image for you, the pay is $150,  you can contact me via email or post comments in this article. Thank you.

************************************

Hope you enjoy this!