1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * Additional copyright for this file:
8  * Copyright (C) 1995-1997 Presto Studios, Inc.
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23  *
24  */
25 
26 #include "common/system.h"
27 #include "graphics/surface.h"
28 #include "video/qt_decoder.h"
29 #include "video/video_decoder.h"
30 
31 #include "pegasus/movie.h"
32 
33 namespace Pegasus {
34 
Movie(const DisplayElementID id)35 Movie::Movie(const DisplayElementID id) : Animation(id) {
36 	_video = 0;
37 	setScale(600);
38 }
39 
~Movie()40 Movie::~Movie() {
41 	releaseMovie();
42 }
43 
44 // *** Make sure this will stop displaying the movie.
45 
releaseMovie()46 void Movie::releaseMovie() {
47 	if (_video) {
48 		delete _video;
49 		_video = 0;
50 		disposeAllCallBacks();
51 		deallocateSurface();
52 	}
53 
54 	setBounds(Common::Rect(0, 0, 0, 0));
55 }
56 
initFromMovieFile(const Common::String & fileName,bool transparent)57 void Movie::initFromMovieFile(const Common::String &fileName, bool transparent) {
58 	_transparent = transparent;
59 
60 	releaseMovie();
61 	_video = new Video::QuickTimeDecoder();
62 	if (!_video->loadFile(fileName)) {
63 		// Replace any colon with an underscore, since only Mac OS X
64 		// supports that. See PegasusEngine::detectOpeningClosingDirectory()
65 		// for more info.
66 		Common::String newName(fileName);
67 		if (newName.contains(':'))
68 			for (uint i = 0; i < newName.size(); i++)
69 				if (newName[i] == ':')
70 					newName.setChar('_', i);
71 
72 		if (!_video->loadFile(newName))
73 			error("Could not load video '%s'", fileName.c_str());
74 	}
75 
76 	Common::Rect bounds(0, 0, _video->getWidth(), _video->getHeight());
77 	sizeElement(_video->getWidth(), _video->getHeight());
78 	_movieBox = bounds;
79 
80 	if (!isSurfaceValid())
81 		allocateSurface(bounds);
82 
83 	setStart(0, getScale());
84 	TimeBase::setStop(_video->getDuration().convertToFramerate(getScale()).totalNumberOfFrames(), getScale());
85 }
86 
redrawMovieWorld()87 void Movie::redrawMovieWorld() {
88 	if (_video && _video->needsUpdate()) {
89 		const Graphics::Surface *frame = _video->decodeNextFrame();
90 
91 		if (!frame)
92 			return;
93 
94 		// Make sure we have a surface in the current pixel format
95 		Graphics::Surface *convertedFrame = 0;
96 
97 		if (frame->format != g_system->getScreenFormat()) {
98 			convertedFrame = frame->convertTo(g_system->getScreenFormat());
99 			frame = convertedFrame;
100 		}
101 
102 		// Copy to the surface using _movieBox
103 		uint16 width = MIN<int>(frame->w, _movieBox.width());
104 		uint16 height = MIN<int>(frame->h, _movieBox.height());
105 
106 		for (uint16 y = 0; y < height; y++)
107 			memcpy((byte *)_surface->getBasePtr(_movieBox.left, _movieBox.top + y), (const byte *)frame->getBasePtr(0, y), width * frame->format.bytesPerPixel);
108 
109 		if (convertedFrame) {
110 			convertedFrame->free();
111 			delete convertedFrame;
112 		}
113 
114 		triggerRedraw();
115 	}
116 }
117 
draw(const Common::Rect & r)118 void Movie::draw(const Common::Rect &r) {
119 	Common::Rect worldBounds = _movieBox;
120 	Common::Rect elementBounds;
121 	getBounds(elementBounds);
122 
123 	worldBounds.moveTo(elementBounds.left, elementBounds.top);
124 	Common::Rect r1 = r.findIntersectingRect(worldBounds);
125 
126 	Common::Rect r2 = r1;
127 	r2.translate(_movieBox.left - elementBounds.left, _movieBox.top - elementBounds.top);
128 	drawImage(r2, r1);
129 }
130 
moveMovieBoxTo(const CoordType h,const CoordType v)131 void Movie::moveMovieBoxTo(const CoordType h, const CoordType v) {
132 	_movieBox.moveTo(h, v);
133 }
134 
setStop(const TimeValue stopTime,const TimeScale scale)135 void Movie::setStop(const TimeValue stopTime, const TimeScale scale) {
136 	TimeBase::setStop(stopTime, scale);
137 
138 	if (_video)
139 		_video->setEndTime(Audio::Timestamp(0, _stopTime, _stopScale));
140 }
141 
setVolume(uint16 volume)142 void Movie::setVolume(uint16 volume) {
143 	if (_video)
144 		_video->setVolume(MIN<uint>(volume, 0xFF));
145 }
146 
setTime(const TimeValue time,const TimeScale scale)147 void Movie::setTime(const TimeValue time, const TimeScale scale) {
148 	if (_video) {
149 		// Don't go past the ends of the movie
150 		Common::Rational timeFrac = Common::Rational(time, ((scale == 0) ? getScale() : scale));
151 
152 		if (timeFrac < Common::Rational(_startTime, _startScale))
153 			timeFrac = Common::Rational(_startTime, _startScale);
154 		else if (timeFrac >= Common::Rational(_stopTime, _stopScale))
155 			return;
156 
157 		_video->seek(Audio::Timestamp(0, timeFrac.getNumerator(), timeFrac.getDenominator()));
158 		_time = timeFrac;
159 		_lastMillis = 0;
160 	}
161 }
162 
setRate(const Common::Rational rate)163 void Movie::setRate(const Common::Rational rate) {
164 	if (_video) {
165 		_video->setRate(rate);
166 
167 		TimeBase::setRate(_video->getRate());
168 		return;
169 	}
170 
171 	TimeBase::setRate(rate);
172 }
173 
start()174 void Movie::start() {
175 	if (_video)
176 		_video->start();
177 
178 	TimeBase::start();
179 }
180 
stop()181 void Movie::stop() {
182 	if (_video)
183 		_video->stop();
184 
185 	TimeBase::stop();
186 }
187 
resume()188 void Movie::resume() {
189 	if (_paused) {
190 		if (_video)
191 			_video->pauseVideo(false);
192 
193 		_paused = false;
194 	}
195 }
196 
pause()197 void Movie::pause() {
198 	if (isRunning() && !_paused) {
199 		if (_video)
200 			_video->pauseVideo(true);
201 
202 		_paused = true;
203 		_pauseStart = g_system->getMillis();
204 	}
205 }
206 
getDuration(const TimeScale scale) const207 TimeValue Movie::getDuration(const TimeScale scale) const {
208 	// Unlike TimeBase::getDuration(), this returns the whole duration of the movie
209 	// The original source has a TODO to make this behave like TimeBase::getDuration(),
210 	// but the problem is that too much code requires this function to behave this way...
211 
212 	if (_video)
213 		return _video->getDuration().convertToFramerate(((scale == 0) ? getScale() : scale)).totalNumberOfFrames();
214 
215 	return 0;
216 }
217 
updateTime()218 void Movie::updateTime() {
219 	// The reason why we overrode TimeBase's updateTime():
220 	// Again, avoiding timers and handling it here
221 	if (_video && _video->isPlaying() && !_video->isPaused()) {
222 		redrawMovieWorld();
223 
224 		uint32 startTime = _startTime * getScale() / _startScale;
225 		uint32 stopTime = _stopTime * getScale() / _stopScale;
226 		uint32 actualTime = CLIP<int>(_video->getTime() * getScale() / 1000, startTime, stopTime);
227 
228 		// HACK: Due to the inaccuracy of the time, we won't actually allow us to hit
229 		// the stop time unless we've actually reached the end of the segment.
230 		if (actualTime == stopTime && !_video->endOfVideo())
231 			actualTime--;
232 
233 		_time = Common::Rational(actualTime, getScale());
234 	}
235 }
236 
GlowingMovie(const DisplayElementID id)237 GlowingMovie::GlowingMovie(const DisplayElementID id) : Movie(id) {
238 	_glowing = false;
239 }
240 
draw(const Common::Rect & r)241 void GlowingMovie::draw(const Common::Rect &r) {
242 	// Make sure the rectangles are clipped properly, OR guarantee that _bounds will
243 	// never fall off the screen.
244 	if (_glowing) {
245 		Common::Rect bounds;
246 		getBounds(bounds);
247 
248 		copyToCurrentPortTransparentGlow(_movieBox, bounds);
249 	} else {
250 		Movie::draw(r);
251 	}
252 }
253 
setBounds(const Common::Rect & r)254 void GlowingMovie::setBounds(const Common::Rect &r) {
255 	Common::Rect bounds;
256 	getBounds(bounds);
257 
258 	if (r != bounds) {
259 		// Avoid Movie::setBounds.
260 		// clone2727 asks why, but goes along with it
261 		Animation::setBounds(r);
262 	}
263 }
264 
ScalingMovie(const DisplayElementID id)265 ScalingMovie::ScalingMovie(const DisplayElementID id) : GlowingMovie(id) {
266 }
267 
draw(const Common::Rect &)268 void ScalingMovie::draw(const Common::Rect &) {
269 	// Make sure the rectangles are clipped properly, OR guarantee that _bounds will
270 	// never fall off the screen.
271 
272 	Common::Rect bounds;
273 	getBounds(bounds);
274 
275 	if (_glowing)
276 		scaleTransparentCopyGlow(_movieBox, bounds);
277 	else
278 		scaleTransparentCopy(_movieBox, bounds);
279 }
280 
281 } // End of namespace Pegasus
282