/* * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program 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. * * This program 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 at /legal/license.txt). * * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package com.sun.mmedia; import javax.microedition.lcdui.Image; import javax.microedition.lcdui.Canvas; import javax.microedition.lcdui.Graphics; import javax.microedition.media.Control; import javax.microedition.media.Player; import javax.microedition.media.PlayerListener; import javax.microedition.media.MediaException; import javax.microedition.media.control.VideoControl; /** * VideoControl implementation for MIDP */ public final class MIDPVideoRenderer extends VideoRenderer implements VideoControl, MIDPVideoPainter { /** If the application requests an Item */ private MMItem mmItem; /** LCDUI notifications registration */ private MMHelper mmHelper = null; /** If the application requests to draw in a Canvas */ private Canvas canvas; /** Full screen mode flag */ private boolean fsmode; /** Is the player closed */ private boolean closed; /** The display mode */ private int mode = -1; /** Container visible flag. True if the Canvas is visible */ private boolean cvis; /** Application specified visibility flag. True if setVisible(true) */ private boolean pvis; /** Player which is being controlled */ private BasicPlayer player; /** Display X */ private int dx, tmpdx; /** Display Y */ private int dy, tmpdy; /** Display Width */ private int dw, tmpdw; /** Display Height */ private int dh, tmpdh; /** Source width */ private int videoWidth; /** Source height */ private int videoHeight; /** To check the frame rate */ private static final boolean TRACE_FRAMERATE = false; /** To check the frame rate */ private int frameCount; /** To check the frame rate */ private long frameStartTime = 0; public static final String SNAPSHOT_RGB888 = "rgb888"; public static final String SNAPSHOT_BGR888 = "bgr888"; public static final String SNAPSHOT_RGB565 = "rgb565"; public static final String SNAPSHOT_RGB555 = "rgb555"; public static final String SNAPSHOT_ENCODINGS = SNAPSHOT_RGB888 + " " + SNAPSHOT_BGR888 + " " + SNAPSHOT_RGB565 + " " + SNAPSHOT_RGB555; /** used to protect dx, dy, dw, dh set & read */ private Object dispBoundsLock = new Object(); /* * Locator string used to open the Player instance which this VideoControl is assoicated with. */ private String locatorString; /**************************************************************** * VideoControl implementation ****************************************************************/ MIDPVideoRenderer(Player p) { if (p instanceof BasicPlayer) { this.player = (BasicPlayer)p; locatorString = player.getLocator(); } else { System.err.println("video renderer can't work with Players of this class: " + p.toString()); } } private void checkState() { if (mode == -1) throw new IllegalStateException("initDisplayMode not called yet"); } public Object initDisplayMode(int mode, Object container) { if (this.mode != -1) throw new IllegalStateException("mode is already set"); if (mode == USE_DIRECT_VIDEO) { if (!(container instanceof Canvas)) throw new IllegalArgumentException( "container needs to be a Canvas for USE_DIRECT_VIDEO mode"); if (mmHelper == null) { mmHelper = MMHelper.getMMHelper(); if (mmHelper == null) throw new IllegalArgumentException( "unable to set USE_DIRECT_VIDEO mode"); } this.mode = mode; fsmode = false; cvis = true; canvas = (Canvas) container; mmHelper.registerPlayer(canvas, this); setVisible(false); // By default video is not shown in USE_DIRECT_VIDEO mode return null; } else if (mode == USE_GUI_PRIMITIVE) { if (container != null && (!(container instanceof String) || !(container.equals("javax.microedition.lcdui.Item")))) throw new IllegalArgumentException("container needs to be a javax.microedition.lcdui.Item for USE_GUI_PRIMITIVE mode"); this.mode = mode; fsmode = false; cvis = true; mmItem = new MMItem(); setVisible(true); return mmItem; } else { throw new IllegalArgumentException("unsupported mode"); } } public void setDisplayLocation(int x, int y) { checkState(); // Applicable only in USE_DIRECT_VIDEO mode if (mode == USE_DIRECT_VIDEO) { if (fsmode) { // Just store location in fullscreen mode synchronized (dispBoundsLock) { tmpdx = x; tmpdy = y; } } else { synchronized (dispBoundsLock) { dx = x; dy = y; } if (pvis && cvis) canvas.repaint(); } } } public int getDisplayX() { return dx; } public int getDisplayY() { return dy; } /** * Check for the image snapshot permission. * * @exception SecurityException if the permission is not * allowed by this token */ private void checkPermission() throws SecurityException { try { PermissionAccessor.checkPermissions(locatorString, PermissionAccessor.PERMISSION_SNAPSHOT); } catch (InterruptedException e) { throw new SecurityException("Interrupted while trying to ask the user permission"); } } public void setVisible(boolean visible) { checkState(); pvis = visible; if (canvas != null) // USE_DIRECT_VIDEO canvas.repaint(); else if (mmItem != null) // USE_GUI_PRIMITIVE mmItem.refresh(false); } public void setDisplaySize(int width, int height) throws javax.microedition.media.MediaException { checkState(); if (width < 1 || height < 1) throw new IllegalArgumentException("Invalid size"); boolean sizeChanged = (dw != width || dh != height); if (fsmode) { // Just store sizes in fullscreen mode synchronized (dispBoundsLock) { tmpdw = width; tmpdh = height; } } else { synchronized (dispBoundsLock) { dw = width; dh = height; } scaleToDest(); if (pvis) if (mmItem != null) mmItem.refresh(true); else if (cvis) canvas.repaint(); } // Makes sense only if NOT in Full Screen mode if (sizeChanged && !fsmode) player.sendEvent(PlayerListener.SIZE_CHANGED, this); } public void setDisplayFullScreen(boolean fullScreenMode) throws javax.microedition.media.MediaException { checkState(); if (fsmode != fullScreenMode) { fsmode = fullScreenMode; if (fsmode) { //switching from Normal to Full Screen synchronized (dispBoundsLock) { tmpdx = dx; tmpdy = dy; tmpdw = dw; tmpdh = dh; } if (mode == USE_DIRECT_VIDEO) { canvas.setFullScreenMode(true); } else { canvas = mmItem.toFullScreen(this, this); if (canvas == null) { // No owner or no display - thus invisible // Do nothing, but simulate fullscreen (lock sizes - for compliance) return; } } synchronized (dispBoundsLock) { dx = 0; dy = 0; // Keep aspect ratio int scrw = canvas.getWidth(); int scrh = canvas.getHeight(); dw = scrh * videoWidth / videoHeight; if (dw > scrw) { dw = scrw; dh = scrw * videoHeight / videoWidth; dy = (scrh - dh) / 2; } else { dh = scrh; dx = (scrw - dw) / 2; } } scaleToDest(); if (cvis) canvas.repaint(); } else { //switching from Full to Normal Screen synchronized (dispBoundsLock) { dx = tmpdx; dy = tmpdy; dw = tmpdw; dh = tmpdh; } scaleToDest(); if (mode == USE_DIRECT_VIDEO) { canvas.setFullScreenMode(false); if (pvis && cvis) canvas.repaint(); } else { mmItem.toNormal(); canvas = null; if (pvis) mmItem.refresh(false); } } player.sendEvent(PlayerListener.SIZE_CHANGED, this); } } public int getDisplayWidth() { checkState(); return dw; } public int getDisplayHeight() { checkState(); return dh; } public int getSourceWidth() { return videoWidth; } public int getSourceHeight() { return videoHeight; } public byte[] getSnapshot(String imageType) throws MediaException, SecurityException { checkState(); checkPermission(); /* REVISIT: Not currently supported. * Need to update video.snapshot.encodings property accordingly * int format = 0, pixelsize = 0; if (imageType == null || imageType.equalsIgnoreCase(SNAPSHOT_RGB888)) { format = 1; pixelsize = 3; } else if (imageType.equalsIgnoreCase(SNAPSHOT_BGR888)) { format = 2; pixelsize = 3; } else if (imageType.equalsIgnoreCase(SNAPSHOT_RGB565)) { format = 3; pixelsize = 2; } else if (imageType.equalsIgnoreCase(SNAPSHOT_RGB555)) { format = 4; pixelsize = 2; } else */ throw new MediaException("Image format " + imageType + " not supported"); /* if (rgbData == null) throw new IllegalStateException("No image available"); byte [] arr = new byte[pixelsize * rgbData.length]; int idx = 0; switch (format) { case 1: // RGB888 for (int i = 0; i < rgbData.length; i++) { arr[idx++] = (byte)((rgbData[i] >> 16) & 0xFF); arr[idx++] = (byte)((rgbData[i] >> 8) & 0xFF); arr[idx++] = (byte)(rgbData[i] & 0xFF); } break; case 2: // BGR888 for (int i = 0; i < rgbData.length; i++) { arr[idx++] = (byte)((rgbData[i] >> 16) & 0xFF); arr[idx++] = (byte)((rgbData[i] >> 8) & 0xFF); arr[idx++] = (byte)(rgbData[i] & 0xFF); } break; case 3: // RGB565 for (int i = 0; i < rgbData.length; i++) { int r = (rgbData[i] >> 19) & 0x1F; int g = (rgbData[i] >> 10) & 0x3F; int b = (rgbData[i] >> 3) & 0x1F; arr[idx++] = (byte)((r << 3) | (g >> 3)); arr[idx++] = (byte)((g << 5) | b); } break; case 4: // RGB555 for (int i = 0; i < rgbData.length; i++) { int r = (rgbData[i] >> 19) & 0x1F; int g = (rgbData[i] >> 11) & 0x1F; int b = (rgbData[i] >> 3) & 0x1F; arr[idx++] = (byte)((r << 2) | (g >> 3)); arr[idx++] = (byte)((g << 5) | b); } break; } return arr; */ } /*private int tryParam(String tok, String prop, int def) { if (tok.startsWith(prop)) { tok = tok.substring(prop.length(), tok.length()); try { return Integer.parseInt(tok); } catch (NumberFormatException nfe) { } } return def; }*/ /**************************************************************** * Rendering interface ****************************************************************/ //private int colorMode; //private boolean nativeRender; private boolean useAlpha; private int [] rgbData; private int [] scaledData; private boolean scaled; private volatile boolean painting; // to prevent deadlocks //private Image image; public Control getVideoControl() { return this; } public void initRendering(int mode, int width, int height) { //colorMode = mode & 0x7F; // mask out NATIVE_RENDER //nativeRender = (mode & NATIVE_RENDER) > 0; useAlpha = (mode & USE_ALPHA) > 0; if (width < 1 || height < 1) throw new IllegalArgumentException("Positive width and height expected"); if ((mode & ~(USE_ALPHA | NATIVE_RENDER)) != XRGB888) throw new IllegalArgumentException("Only XRGBA888 mode supported"); videoWidth = width; videoHeight = height; // Default display width and height synchronized (dispBoundsLock) { dw = videoWidth; dh = videoHeight; } rgbData = null; scaledData = null; scaled = false; painting = false; //image = null; } /** * Public render method */ public void render(int[] colorData) { rgbData = colorData; scaleToDest(); if (!pvis) return; if (canvas != null) { if (cvis) { canvas.repaint(dx, dy, dw, dh); } } else if (mmItem != null) { mmItem.refresh(false); } } /** * Public render method */ public void render(byte[] colorData) { throw new IllegalStateException("Only 32 bit pixel format supported"); } /** * Public render method */ public void render(short[] colorData) { throw new IllegalStateException("Only 32 bit pixel format supported"); } public void close() { if (!closed && canvas != null) mmHelper.unregisterPlayer(canvas, this); if (rgbData != null) synchronized (rgbData) { rgbData = null; scaledData = null; } //image = null; closed = true; } /** * Scales an input rgb image to the destination size. */ private void scaleToDest() { int ldw = 0; int ldh = 0; synchronized (dispBoundsLock) { ldw = dw; ldh = dh; } if (rgbData != null) synchronized (rgbData) { // To avoid interference with close() scaled = ldw != videoWidth || ldh != videoHeight; if (scaled) { if (scaledData == null || scaledData.length < ldw * ldh) scaledData = new int[ldw * ldh]; // Scale using nearest neighbor int dp = 0; for (int y = 0; y < ldh; y++) for (int x = 0; x < ldw; x++) scaledData[dp++] = rgbData[((y * videoHeight) / ldh) * videoWidth + ((x * videoWidth) / ldw)]; } } } /** * Scale an image to the destination size. This first gets the * pixels from the image and then uses the other scaleToDist() * to do the scaling. */ /*private void scaleToDest(Image img) { if (rgbData == null) rgbData = new int[videoWidth * videoHeight]; int width = img.getWidth(); int height = img.getHeight(); // REVISIT: width and height need to be stored... img.getRGB(rgbData, 0, videoWidth, 0, 0, width, height); scaleToDest(); }*/ /**************************************************************** * MIDPVideoPainter interface ****************************************************************/ /** * Paint video into canvas - in USE_DIRECT_VIDEO mode */ public void paintVideo(Graphics g) { // Don't paint if Canvas visible flag is false if (!pvis || !cvis || painting) return; painting = true; // Save the clip region int cx = g.getClipX(); int cy = g.getClipY(); int cw = g.getClipWidth(); int ch = g.getClipHeight(); // Change the clip to clip the video area g.clipRect(dx, dy, dw, dh); // Check if its within the bounds if (g.getClipWidth() > 0 && g.getClipHeight() > 0 && pvis) { int w = dw, h = dh; if (w > videoWidth) w = videoWidth; if (h > videoHeight) h = videoHeight; try { if (rgbData != null) { synchronized (rgbData) { if (scaled) { g.drawRGB(scaledData, 0, dw, dx, dy, dw, dh, useAlpha); } else { g.drawRGB(rgbData, 0, videoWidth, dx, dy, w, h, useAlpha); } } } } finally { // Revert the clip region g.setClip(cx, cy, cw, ch); painting = false; } } else { g.setClip(cx, cy, cw, ch); painting = false; } if (TRACE_FRAMERATE) { if (frameStartTime == 0) { frameStartTime = System.currentTimeMillis(); } else { frameCount++; if ((frameCount % 30) == 0) { int frameRate = (int) ( (frameCount * 1000) / (System.currentTimeMillis() - frameStartTime + 1)); System.err.println("Frame Rate = " + frameRate); } } } } /** * Enable/disable rendering for canvas (USE_DIRECT_VIDEO mode) */ public void showVideo() { if (canvas != null && !cvis) { cvis = true; canvas.repaint(); } } public void hideVideo() { if (canvas != null && cvis) { cvis = false; canvas.repaint(); } } /**************************************************************** * MMItem (CustomItem) - USE_GUI_PRIMITIVE mode ****************************************************************/ final class MMItem extends MMCustomItem { public MMItem() { super(""); } public void refresh(boolean resize) { if (resize) { invalidate(); repaint(); } else repaint(dx, dy, dw, dh); } protected void paint(Graphics g, int w, int h) { // Don't paint if VideoControl visible flag is false if (!pvis || painting) return; painting = true; if (rgbData != null) { synchronized (rgbData) { if (scaled) { g.drawRGB(scaledData, 0, dw, 0, 0, dw, dh, useAlpha); } else { g.drawRGB(rgbData, 0, videoWidth, 0, 0, videoWidth, videoHeight, useAlpha); } } } painting = false; } protected int getMinContentWidth() { return 1; } protected int getMinContentHeight() { return 1; } protected int getPrefContentWidth(int h) { return dw; } protected int getPrefContentHeight(int w) { return dh; } protected void hideNotify() { super.hideNotify(); } } }