Technical: QT for Java
Advanced Search
Apple Developer Connection
Member Login Log In | Not a Member? Contact ADC

title


Previous Section Table of Contents Next Section


Module 1- Initializing Quicktime and Displaying an Image

Contents

Overview

Introducing QuickTime for Java

Initializing Quicktime and Displaying an Image

  1. Creating the Zoo1 Class

  2. Adding the Constructor

  3. Window Initialization

  4. Initializing QuickTime

  5. Handling Application Termination

  6. Drawing an image using QTJ

Summary

Further Exploration


Overview

Module1In this first module, we will learn the basics of QuickTime for Java, such as initializing QuickTime for Java, creating a window with a QTCanvas, and drawing an image centered within the window. (See image right.)

Additionally, we will familiarize ourselves with some of the principal concepts behind QTJ and explore the architecture. At the end of this module, you will have an knowledge of how to create and prepare an application that uses QuickTime for Java and having a basic understanding of how to use the QTJ API.

Introducing QuickTime for Java

QuickTime for Java is both an API and an application framework. As an API, it provides Java developers access to the rich wealth of multimedia capabilities in Quicktime previously available only to C/C++ and Pascal programmers. It enables access to QuickTime's native runtime libraries which provide support for different forms of media (images, audio, and movies), timing services, media capture, complex compositing, visual effects, and custom controllers. QuickTime for Java has many other benefits as well. Since the API relies on native libraries to perform its complex and time-consuming tasks, it is extremely fast. It is also cross-platform- it will run on all platforms that support QuickTime (Macintosh, Windows NT, Windows 95, and Windows 98).

The application framework layer makes it easier for Java developers to integrate QuickTime capabilities into Java applets and applications. The framework includes:

  • the integration of QuickTime with the Java Runtime. This includes sharing display space between Java and QuickTime for Java and the passing of events between QuickTime and Java.

  • classes that simplify the process of authoring QuickTime content and providing basic multimedia support

We will be taking advantage of both of these layers in the first module of this tutorial.

Initializing QuickTime and Drawing an image

QuickTime for Java is a large API and it is very easy to get lost. Perhaps the best way to learn QuickTime for Java is to jump right in and start writing a simple example. This module is presented in a progressive format as if the code was being written before your eyes. This is designed to help suggest a methodology for programming to the QuickTime for Java API. Newly added code will appear in yellow (yellow) while code from previous steps will appear in gray (gray). Code that is removed or deleted will appear in red (red). We will explain things as we go along, so open up your favorite source editor, and let's start coding!

Creating the Zoo1 Class

Our first step will be creating a window and displaying it on the screen. The easiest way to do this is to create a new class derived from java.awt.Frame that looks something like this:

import java.awt.*; 
public class Zoo1 extends Frame
{
     public static void main( String[] args )
    {
    }
}


So far we haven't done anything tricky. We have a static main method because we want to build an application instead of an applet. Now would be a good time to save your file as "Zoo1.java". The file should be the same name as the main class or your compiler may complain.

Back to top

Adding the Constructor

Next, we will add a constructor that takes a string parameter for the title of the window:

import java.awt.*; 
public class Zoo1 extends Frame
{
    public Zoo1( String s ) 
    {
        super(s);
    }
    
    public static void main( String[] args ) 
    { 
        Zoo1 appWindow = new Zoo1( "QTZoo 1" );
    } 
}


Now we have added our constructor for the Zoo class. The constructor takes a string argument and passes it off to our superclass constructor which is Frame. The Frame class will automatically handle initializing the window title for us based on our string argument. We have also created an instance of our object in main( ), and passed a window title to the constructor. Now we are ready to perform our initialization.

Back to top

Window Initialization

Now we need to configure the parameters of the window such as the size and resizability. Once again, this is all standard Java AWT, so we are not going to explain most of this in great detail:

import java.awt.*; 
public class Zoo1 extends Frame
{
    static public int WIDTH  = 640;
    static public int HEIGHT = 480;	
    public Zoo1( String s ) 
    {
        super(s);
        setResizable( false );              
        setBounds( 0, 0, WIDTH, HEIGHT );  
    }

    public static void main( String[] args ) 
    { 
        Zoo1 appWindow = new Zoo1( "QTZoo 1" );
        appWindow.show();
        appWindow.toFront();
    } 
}


We have created two static integers to store information about the height and width of our window. We will use these values in the constructor in which we make the window non-resizable and set the bounds of the window. Finally, in the main method, we call show( ) to make the window visible and call toFront( ) to ensure that it is the front-most window.

It is useful to note that thus far, no special coding to deal with QuickTime has been done. This serves to emphasize the fact that QuickTime for Java is designed to work in a complementary manner with the standard AWT. It does not enforce a specific programming style.

In the next step we will initialize QT.

Back to top

Initializing QuickTime

Since QuickTime is a native library, its memory is managed outside of the normal Java heap. In most cases, you create QuickTime for Java objects in the same manner that you would create a standard Java object. However, before you work with QuickTime for Java, you must allow QuickTime to allocate memory for the native objects associated with your application and initialize its internal data structures.

This is accomplished through one API call that is part of the quicktime.QTSession package:

import java.awt.*; 
Import quicktime.QTSession;
import quicktime.QTException;

public class Zoo1 extends Frame
{
    static public int WIDTH  = 640;
    static public int HEIGHT = 480;	
    public Zoo1( String s ) 
    {
        super(s);
        setResizable( false );              
        setBounds( 0, 0, WIDTH, HEIGHT ); 
    }
 
    public static void main( String[] args ) 
    { 
        try
        {
            QTSession.open();
            Zoo1 appWindow = new Zoo1( "QTZoo 1" );
        
            appWindow.show();
            appWindow.toFront();
        }
        catch ( Exception e )	
        {
            QTSession.close();
            e.printStackTrace();
        }
    } 
}


We have included two packages at the beginning of our application. The first, the QTSession package allows us to open and close QuickTime sessions between Java and the native QuickTime library. The second is for QTException which is thrown by many QTJ calls if error conditions arise.

After adding the two import statements, we add a try / catch block. This is necessary because our call to open the QTSession which initializes QuickTime for Java could throw an Exception (for example if QuickTime is not installed). If we don't catch this, we will get a compiler error because the static method QTSession.open( ) is declared as throwing an Exception.

We attempt to open the session, and if any error occurs, we close the session in our catch block by calling the static method QTSession.close( ).

The key point of interest in this step is that we must call QTSession.open( ) to initialize QuickTime. At the end of our application, we should call QTSession.close( ) to perform cleanup and properly shut down our QuickTime session. You will notice that we are only closing down QTJ if an error condition occurs. We should also close our session if the user quits the application. We will handle this case in the following step.

Back to top

Handling Application Termination

When the user quits our application by closing our window, or choosing Quit from the Apple Menu, we need to shut down QTJ:

import 
            java.awt.*; 
Import java.awt.event.*;

Import quicktime.QTSession;
import quicktime.QTException;

public class Zoo1 extends Frame
{
    static public int WIDTH  = 640;
    static public int HEIGHT = 480;	
    public Zoo1( String s ) 
    {
        super(s);
        setResizable( false );              
        setBounds( 0, 0, WIDTH, HEIGHT ); 

        addWindowListener( new WindowAdapter() 		
        {
            public void windowClosing( WindowEvent we )
            {
                QTSession.close();		
                dispose();					
            }
            public void windowClosed( WindowEvent we )
            {
                System.exit( 0 );		
            }
        });
    }
 
    public static void main( String[] args ) 
    { 
        try
        {
            QTSession.open();
            Zoo1 appWindow = new Zoo1( "QTZoo 1" );
        
            appWindow.show();
            appWindow.toFront();
        }
        catch ( Exception e )	
        {
            QTSession.close();
            e.printStackTrace();
        }
    } 
}


We have added an import statement for java.awt.event so that we can create our new WindowAdapter. Our window adapter code is a bit tricky. We are taking advantage of a Java's support for a feature called anonymous inner classes. This allows us to create a new subclass of WindowAdapter and override two of its methods from inside the call addWindowListener( ) which is part of Frame. This allows us to receive notification when the close box is pressed via the windowClosing( ) method and when the window has been destroyed via the windowClosed( ) method.

In the windowClosing( ) method we shut down the QTSession and dispose of our window. In windowClosed( ) we call System.exit( ) which quits our program.

You will notice that we did not do anything special to handle the quit menu item. On Windows, choosing quit causes close messages to get sent to any open windows (which in our case would close QTJ because of our WindowAdapter class). On the Mac, where this isn't the case, a default quit handler is installed for us by QTJ that automatically calls QTSession.close( ) as the application terminates.

Now the only task that remains is drawing our image.

Back to top

Drawing an image using QTJ

Now comes the fun part- opening an image file and using QuickTime to display it in our window. First we need to import a few packages:

import java.awt.*; 
Import java.awt.event.*; 
Import java.io.IOException;

import quicktime.QTSession;
import quicktime.QTException;
import quicktime.app.image.GraphicsImporterDrawer;
import quicktime.app.display.QTCanvas;
import quicktime.app.QTFactory;

import quicktime.io.QTFile;


These are mostly QuickTime packages which we will be using to load and draw our images. For example, the GraphicsImporterDrawer class allows QuickTime to parse a number of image file formats and draw the image. We also need IOException because QT will throw this type if the file specified is not found. We will talk about these classes in the code section below:


public class Zoo1 extends Frame
{
    static public int WIDTH  = 640;
    static public int HEIGHT = 480;	
    public Zoo1( String s ) 
    {
        super(s);
        setResizable( false );              
        setBounds( 0, 0, WIDTH, HEIGHT );
       
        QTCanvas myQTCanvas = new QTCanvas( 
                 QTCanvas.kInitialSize, 0.5F, 0.5F );
        add( myQTCanvas );


Here we are adding drawing functionality to our window. For simplicity's sake, we have decided to place this code directly in the constructor. Our first step is to create a QTCanvas. To present QuickTime content in a Java Applet or Application, you need a mechanism for interacting with the display and the event system. The QTCanvas is a specialized canvas that provides access to QuickTime's native drawing mechanism within a Java window. It essentially punches a hole in the Java display area and manages display of that region using QuickTime.

All QuickTime drawing must occur in a QTCanvas. When we create a QTCanvas here, we are specifying an area of our window where QuickTime will perform imaging. The constructor of the QTCanvas takes three arguments. The first specifies how the QTCanvas should behave when its container resizes. In this example, we have specified the constant QTCanvas.kInitialSize. With this sizing flag, if our window grows larger than the size of the canvas, the canvas does not scale. It remains at its original specified size. Similarly, if the window becomes smaller than the canvas, the canvas does not scale either. It will be clipped to the window size. There are other flags that allow you to have free scaling as well as ones that allow scaling but preserve aspect ratio. These are all defined in QTCanvas.

The second and third parameters of the QTCanvas constructor are for horizontal and vertical alignment. These specify the default position of items drawn within the canvas. We have specified the float value of 0.5 for both the horizontal and vertical alignment parameter. This will center items within the Canvas. A value of zero will specify the top or left and the value of one will specify bottom or right.

For example, let's pretend that we are creating a window and placing a QTCanvas inside it. Let's also pretend that our client (QT object that is capable of drawing itself in a QTCanvas) is 125 by 125 pixels (which is slightly smaller than the window we are creating). The following series of images demonstrates the behavior we would see based on the values of the parameters we pass the QTCanvas. Note that the QTCanvas is invisible. It is the area that drawing may occur in. The green area with the text QTCanvas is the client of the Canvas that is doing the drawing:

QTCanvas( QTCanvas.kInitialSize, 0.5F, 0.5F )

Remember that the canvas is the same size as the window. The client area is smaller than the QTCanvas it lives in. By specifying kInitial size, no scaling occurs. The two float values specify that the client is to be drawn centered within the QTCanvas. If our window was smaller than the client, part of the client would not fit in the window. No scaling would occur.

QTCanvas( QTCanvas.kInitialSize, 0F, 0F )

This is very similar to the first example except that by specifying 0F, 0F, we are telling the canvas to draw it's client object in the upper left hand corner. Like the previous example, if the window were resized, no scaling would occur.

QTCanvas( QTCanvas.kFreeResize, 0F, 0F )

In this example, we have specified the kFreeResize parameter. This will cause the QTCanvas to resize with the window, and will also cause the content area of the QTCanvas to freely scale as the QTCanvas changes sizes.


For our application, we want the images to be centered in our window and not scale (even if they are too big to fit in the window). These parameters may easily be changed at runtime by calling the QTCanvas methods setAlignment( ), and setResizeFlag( ).

Once we have created the QTCanvas, we add it to the window by calling add( ) from Component.

Now that we have a QTCanvas to draw into, we need to load the image files and display them:

        add( myQTCanvas );
       
        try
        {
            QTFile imageFile = new QTFile( 
                QTFactory.findAbsolutePath( 
                "data/zebra/ZebraBackground.jpg" ));


We need to put all of our code in a try block, because QuickTime could throw an exception if the media is not found. Now we create a new QTFile object that represents the location of our zebra image. Since QTFile's constructor can take a file, and we don't want to hard code an absolute path (which will break if our application moves), we are using QTFactory's findAbsolutePath( ) to locate the image file based on a relative path and create a File object that we can pass to our QTFile constructor.

Now that we have a reference to the QuickTime media file, we need to load it and draw it:

            QTFile imageFile = new QTFile( 
                QTFactory.findAbsolutePath( 
                "data/zebra/ZebraBackground.jpg" ));
            GraphicsImporterDrawer mapDrawer = 
                new GraphicsImporterDrawer( imageFile );
            myQTCanvas.setClient( mapDrawer, true );


We declare a new GraphicsImporterDrawer object. This object is responsible for examining the media file, decompressing the file (if necessary), importing it into a native format that can be easily drawn, and preparing it for drawing. If the media file is in one of the many formats that QuickTime supports, the GraphicsImporterDrawer object is capable of loading the file and displaying it in a QTCanvas.

Once we have loaded the media file, we call setClient( ) from the QTCanvas with that object as a parameter. This tells the canvas that our GraphicsImporterDrawer is responsible for imaging the contents of the QTCanvas. Thus, when the area of the window that contains the QTCanvas needs to be redrawn, the QTCanvas will notify its client (the GraphicsImporterDrawer) that it needs to draw.

That's all that needs to be done! The code that we just wrote is sufficient to load and draw a media file regardless of the format! Now we will take a step back and look at the whole constructor and add some exception handling:

    public Zoo1( String s ) 
    {
        super(s);
        setResizable( false );              
        setBounds( 0, 0, WIDTH, HEIGHT ); 

        QTCanvas myQTCanvas = new QTCanvas( 
                 QTCanvas.kInitialSize, 0.5F, 0.5F );
        add( myQTCanvas );

        try
        {
            QTFile imageFile = new QTFile( 
                QTFactory.findAbsolutePath( 
                "data/zebra/ZebraBackground.jpg" ));
            GraphicsImporterDrawer mapDrawer = 
                new GraphicsImporterDrawer( imageFile );
            myQTCanvas.setClient( mapDrawer, true );
        }
        catch ( IOException e )
        {
             e.printStackTrace();
        }
        catch ( QTException e )
        {
             e.printStackTrace();
        }         
        addWindowListener( new WindowAdapter() 		
        {
            public void windowClosing( WindowEvent we )
            {
                QTSession.close();		
                dispose();					
            }
            public void windowClosed( WindowEvent we )
            {
                System.exit( 0 );		
            }
        });
    }


Both the QTFile constructor, and the findAbsolutePath( ) routine can throw IOExceptions. In addition, the GraphicsImporterDrawer constructor can throw a QTException. Thus, it is vital that we catch these exceptions. Note that we are not doing much if we get an error. We are just printing a stack trace. In a real application, you would probably want to display some kind of dialog to alert the user of the problem. For the purposes of this tutorial, this level of error handling is sufficient.

That is all there is to it! Click on this link if you would like to see the entire source file.

Back to top

Summary

This module represents our first foray into the world of QuickTime for Java. As such, we are taking tentative steps. We learned how to initialize QuickTime for Java, and how to shut down our QTSession when the user quits or an error occurs. We learned how the QTCanvas works, and how to create a canvas and install it in our window. Finally, we learned how to load and display media files using a GraphicsImporterDrawer and set it to be the client of the canvas. That is actually quite a bit for such a small example.

Looking back, there is actually not very much code required to do what we are doing. It would take about the same code to get similar functionality using just the AWT, but the advantage of using QuickTime for Java is that not only do we have the flexibility of a variety of image formats (which there is no support for in Java such as PNG, Photoshop, Pict, Targa, TIFF, and bmp to name a few), but we can also render the media very quickly. That is the heart of Quicktime for Java- flexibility, and performance.

Back to top

Further Exploration

The clever reader will realize quickly that the information that was presented here only scratches the surface of what could be done using QuickTime for Java. Although we will expand this example in future modules, there are few things that you could do on your own as exploration that will help you better understand how QuickTime for Java works.

Each one of these examples has a source code solution, so if you have trouble, feel free to consult it.

Experiment with one or more of the following:

  1. Make the window resizable and see how it affects the way the image displays
  2. Make the image resize as the window gets bigger
  3. Make the image scale proportionally if the window size is too small to display the image, but don't allow the image to grow larger than its original size.
  4. Display your own images instead of the zebra. (What happens if you specify a movie instead of an image?)

 



Previous Section Table of Contents Next Section