• 20030602: Implemented the MLJpegSource abstract class, a C++ wrapper around the "source manager"
concept of the IJG library. MLJpegFileSource, a concrete subclass of MLJpegSource, provides a
buffered source that can be constructed from an MLPath object. Why? After all, the IJG library already
provides a default source manager based on std::FILE. Yes, but we'd rather avoid using ANSI file functions
on Mac OS 9 -- for example, std::fopen() can become ambiguous on OS 9 if two mounted volumes have the same name.
• 20030603: Added rudimentary (but adequate) Exif support to the IJG library-based MLJpegDecompressor class.
The IJG library can actually gather "APP1" markers found in the JPEG file header into a linked list
(see jpeg_save_markers()) so it already does half of the job for us. The other half is just a matter of
parsing the Exif header and the TIFF-style IFD (Image File Description) directories. We currently recognize
only a handful of Exif tags, collect these in a MLPropertyListDict dictionary, and discard all the others.
Yet the basic parsing infrastructure is there and can be easily extended to support more tags.
• 20030603: Implemented the compression side of the IJG library, in the form of a new MLJpegCompressor class,
along with the ancillary MLJpegDestination and MLJpegFileDestination helper classes.
Tried substituting a MLJpegCompressor object for a MLGraphicsExporter in DigitalEmotionPhoto::MakeThumbnail(),
and everything seems to work peachy-keen. The resulting JPEG files are even significantly smaller than the
one created by QuickTime, using comparable quality settings (codecHighQuality = 75%).
• 20030604: Small revolution in the framework: the MLTime data type, defined in "MLTypes.h" and used to
represents times and durations, is now a double-precision floating point rather than an unsigned 32-bit
integer. Also, on the Mac OS, the MLDispatcher::GetTime() function is now implemented using
Microseconds() rather than TickCount(), which has a very coarse resolution (1/60 of a second).
All MLTime values are still expressed in milliseconds.
On Mac OS 9, the Microseconds() clock and the TickCount() clock (used by the Event Manager to timestamp
events) can go out of sync, so MLDispatcher now automatically adjusts the mWhen field of MLMessage
records to compensate for the drift, if any.
The JPEG compressor and decompressor classes can now yield the cpu to other cooperative threads during
lengthy operations, by default once every 10 ms (this can be changed using MLJpegCompressor::SetTimeSlice()).
This feature is controlled by the ML_JPG_TIME_SLICES compile-time switch.
The default 10 ms time slice appears to be small enough for the importer thread to run quietly in the
background while keeping the main thread very responsive to user interaction.
This change addresses [entry #3 of v2 specs].
• 20030605: Integrated latest mods from Andrea (bilinear filter). "Bilinear" can now be chosen from the
"Interpolation Method" pop-up menu in the Print Options dialog. This filter sits between nearest neighbor
and bicubic, both quality- and speed-wise.
New build posted to my website:
<http://www.merzwaren.com/bin/digital-emotion-20030605.sit>
• 20030605: Ideas for making DE even more responsive while the importer thread is running:
1. The time slice used by JPEG compressors/decompressors should depend on the thread they're
running in: a small slice (say, 10 to 50 ms) when running in the importer thread, but a big
slice, or no slice at all (just hog the cpu until done) when running in the main thread.
Added the static function MLMacThread::IsMainThread() for this purpose.
2. Right now, the main thread yields the cpu to other threads (in MLDispatcher::DispatchMacNullMessage)
as often as possible. This may not always be a good idea. Added a MLDispatcher::SetYieldThrottle()
method that allows you to specify the minimum period between successive yields.
3. MLDispatcher::DispatchMacNullMessage() should call DispatchCursor() *first*, then yield the cpu,
not the other way around.
With these changes, the application feels more responsive, although that's just my subjective feeling.
• 20030606: Added MLPath::DeleteFolder(). This method deletes the (empty) directory pointed to by the
given path. Once the directory is successfully deleted, the MLPath object points to a missing node
in the file system tree where the directory used to be, and can be immediately re-used to create a file
or another directory with the same name.
• 20030606: Implemented the FORMAT button. [entry #10 of v2 specs]
Clicking this button causes DE to enter a card-erasing mode: all files and directories on volumes mounted
when this mode is in effect are deleted recursively (but the volumes themselves are not actually formatted).
When in card-erasing mode, a modal window with a bright yellow background prevents any other activities --
closing this window exits the mode.
• 20030609: I'm sick of having to click DATABASE and Find All every time I open an album, so I've added a
new preference key (kKeyPreferencesFindAllOnOpen == "find_all_on_open") that lets me do that automatically.
The default value is false (as per specs).
• 20030611: Minor cleanup of MLException and related classes: eliminated some unnecessary constructors and
assignment operators. Replaced several occurrences of std::sprintf with the safer std::snprintf.
• 20030613: Started working with Leonardo Pellegrini on Windows port of Digital Emotion.
Added x86 targets to project file and attempted to recompile the ML framework. Several minor tweaks
were needed to get most files to compile correctly. Over the next few days, we're going to need to perform
major adjustments to some classes (e.g., MLDate), and think about designing and implementing Windows
counterparts to some Mac-only classes (e.g., the various system controls: buttons, sliders, clocks, etc.)
• 20030616: WARNING: Important change to MLDate, which has the potential for major impact on existing code:
the default constructor used to initialize the date to the current date (with a potentially expensive and
unneeded system call); to initialize a date to its undefined/invalid state, you had to use the MLDate(SInt64)
constructor, passing an explicit zero. This was a design mistake that I'm trying to undo. From now on,
the default MLDate constructor initializes the date to its undefined/invalid state. To initialize a date
to the current date/time, you now need to use a static accessor, GetCurrentDate(). In other words, uses
like this:
MLDate currentDate ;
must be replaced with:
MLDate currentDate ( MLDate :: GetCurrentDate ( ) ) ;
• 20030616: Mac (Carbon) implementation of volume-mounted and modifiers-changed messages at the framework level,
as two new kinds of MLMessage (MLMessage::kVolumeMounted and MLMessage::kModifiersChanged). The kVolumeMounted
message is generated when a volume is mounted -- the message parameter is a pointer to an MLPath object
representing the newly mounted volume. The kModifiersChanged message is generated when some keyboard
modifier key (like shift or control) is pressed or raised.
The same messages will have to be generated on Windows, although the underlying implementation will
of course be different. Ditto for Mac (Classic), if we still care to support non-Carbon Mac targets.
• 20030617: Tried recompiling the project with the prerelease C++ compiler from CodeWarrior Pro 9 (the forthcoming
major revision to everyone's favorite Integrated Development Environment). Everything compiles mostly fine,
except one thing: local types can no longer be used as template arguments, so code like the following (from
MLListView.cpp) will have to be changed:
void MLStringList :: SortLineStarts ( )
{
class ListComp
{
const char * mTextBase ;
public :
ListComp ( const char * inTextBase ) :
mTextBase ( inTextBase )
{ }
bool operator ( ) ( UInt32 inOffset1, UInt32 inOffset2 ) const
{ return ( :: macstrcmp ( mTextBase + inOffset1, mTextBase + inOffset2 ) < 0 ) ; }
} ;
std :: sort ( mStartOffsets . Begin ( ), mStartOffsets . End ( ) - 1, ListComp ( mText . CStr ( ) ) ) ;
}
• 20030617: Icons and frames can now be PNG files -- previously they could only be pictures in Targa
format. The plan is to switch to PNG-only, at least for the Windows version, since we want to avoid
using QuickTime, and we have libpng already integrated in the framework, but no equivalent for Targa
files. Also, although Targa and PNG are both lossless formats that support full alpha channels, PNG
is much more space-efficient than Targa. PNG is so neat -- gotta love it!
JPEG thumbnails of icons and frames are now generated using the IJG library.
In order to avoid relying on the QuickTime-specific data type "CodecQ" to express the quality/size tradeoff,
MLJpegCompressor::SetQuality() now takes a float parameter in the range 0 (lowest quality -- maximum compression)
to 1 (highest quality -- lossless or near-lossless compression).
• 20030623: Called Sony Italia in an attempt to obtain the Windows SDK for the Sony UP-D70A printer
(which is no longer to be found on Sony's website) and maybe some technical support (source code? specs?) for using
that printer from a Mac OS X application. Hopefully, they'll get back to me in a few days.
Fixed bug which could cause DigitalEmotionApp::ImportPictures() to crash when importing corrupt JPEG files --
a very similar problem in DigitalEmotionImporter was fixed long ago. I should really merge those codepaths,
and perform "manual" import in its own thread, just like automatic import.
It looks like, sometimes, the IJG library tries to read data past the end of a file (this seems to happen
with corrupt, truncated JPEG files). To be safe, MLJpegSource::ReadSource() now zeroes the unused portion
of the returned buffer.
• 20030624: Eventually found out why prints produced by Digital Emotion aren't as good as they were supposed
to be, even using bicubic interpolation (see 20030520 entry in this log). It turns out that the print
canvas is prepared in two phases: first the photo(s) are composed using DigitalEmotionPhoto::ComposeCanvas(),
then they are placed into the print canvas using Quickdraw's CopyBits() (in the A4 case) or DigitalEmotionAlbum::
PlaceOntoPrintCanvas(). Well, the high-quality bicubic interpolation was only used during the first
phase, but got degraded by the subsequent application of inferior scaling algorithms used to place the
photo onto the print canvas.
Consolidated MLAffineTransform, MLAffineTransformBilinear and MLAffineTransformBicubic into a single
class (MLAffineTransform), whose constructor takes an InterpolationType parameter. I think this makes more
sense than having three effect classes that only differ in their implementation of EffectLoop32Bit().
The MLAffineTransformWithAlpha class now takes an InterpolationType parameter, too, and supports bicubic
interpolation (TODO: bilinear interpolation). Its MLAffineTransformWithAlpha::EffectLoop32BitBicubic()
is closely modeled on Andrea's MLAffineTransformBicubic implementation, but blends the transformed image
into the destination canvas, taking its alpha channel into account.
Added DigitalEmotionOverlay::SetAlpha() accessor. When an overlay (icon or text) is selected,
the 't' and 'o' keys (for 'Transparent' and 'Opaque') can be used to decrease or increase the
overlay alpha (tranparency). The default value is 1.0 (opaque) -- an alpha value of 0.0 makes
the overlay effectively invisible.
DigitalEmotionLayout::ApplyLayout() and DigitalEmotionOverlay::DrawOverlay() now take an additional
InterpolationType parameter, the same received by DigitalEmotionPhoto::ComposeCanvas(). This means
we can now use high-quality interpolation when applying overlays. Frames, on the other hand, are still
composed using Quickdraw's CopyBits(), which I think employs an inferior scaling algorithm.
Fixed bug where grand totals weren't being updated in the Print Log window after completing a print job.
• 20030626: Cleanup of MLMsg.h header: messaging definitions (MLCommand, MLMessageID, MLMessage, etc.) were
moved to a new MLMessage.h file; the MLCommander class was moved to a new MLCommander.h file, which
parallels the existing MLCommander.cpp. The definition of the MLMessage class was moved to its own
MLMessage.cpp file (from MLDispatcher.cpp). Finally, MLMsg.h itself and all references to it were removed.
MLMessage now has a richer constructor which allows the specification of a timestamp, a global mouse location,
etc. This constructor is rarely used, and only for synthesized messages -- most MLMessage object are constructed
from MLPlatformMessage structures by MLDispatcher::GetMessage().
Added MLDispatcher accessors for retrieving the current mouse location in screen coordinates (GetCurrentMouse),
and the current state of the modifier keys (GetCurrentModifiers). GetCurrentModifiers returns a bit map
which can be tested with MLMessage::kShiftModifierMask, MLMessage::kAltModifierMask, etc.
• 20030628: Revised the HTML generation code so that it outputs well-formed XHTML 1.1 (XHTML 1.0 Frameset
for the index page). Mostly as a debugging feature, the HTML exporter will now incorporate "validator
links" in the pages it creates if the "html_include_validator_link" key is set to true in the preferences
file. Validator links look like this:
<a href="http://validator.w3.org/check?url=referer">Check this page</a>
• 20030629: Found and fixed a bug in the HTML exporter which was causing a major performance degradation:
DigitalEmotionHTMLExporter::CreateThumbnailPage() was calling DigitalEmotionPhoto::ComposeCanvas(), a
very expensive routine, even when (inMakeThumbnailFile == false).
A new preference key, "html_include_keywords" (boolean, false by default) controls whether the caption
below each photo in the generated HTML galleries includes the corresponding keywords, in square brackets.
The HTML exporter now generates a common, external style sheet that applies to all HTML files, rather
than embedding a style sheet in each and every page. This makes it much easier to change the formatting.
• 20030630: Further optimizations of the bicubic interpolation code in MLAffineTransform::EffectLoop32BitBicubic()
and MLAffineTransformWithAlpha::EffectLoop32BitBicubic(). In particular, the cubic weighting function (cwf)
is now called 8 times per pixel, rather than 20 times per pixel. The new implementation processes about
384 kp/s (kilopixel per second) on my G4/350, vs. about 231 kp/s in the previous implementation. That's 66%
faster! But it's still dog-slow on a full print canvas (3508 x 2550), taking about 22s on my machine.
Extended the expiration date to September 8, 20003.
• 20030701: New build posted to my web site:
<http://www.merzwaren.com/bin/digital-emotion-20030701.sit>
• 20030704: Fixed OBO error in DigitalEmotionHTMLExporter::CreateThumbnailPage(). When the photo count
was an exact multiple of (rowCount * columnCount), we were generating an extra, empty gallery.
• 20030705: Fixed bug in DigitalEmotionApp::DoTrash(). Sometimes, using the TRASH button in PHOTO mode
would display the warning message twice -- this would only happen if the current album had any layouts.
• 20030714: Integrated recent changes to HTML exporter by Andrea Pelizzari (progress bar, stop button).
Moved HTML exporter to its own thread.
Started working on a new MLPeriodicThread class meant to simplify the use of cooperative threads.
The rationale for this class proceeds from the observation that MLMacThread-derived classes are
usually subclasses of MLPeriodical, too. This is necessary to make sure the process gets cpu time
for non-primary cooperative threads even when the main thread sits idle (and would therefore call
WaitNextEvent with a potentially large sleep value, essentially blocking all threads at the process
level). The interaction between the periodical and the thread is clumsy and somewhat confusing.
Also, by default, MLMacThread creates new threads in suspended state, so that they need to be explicitly
Schedule()'d. The new MLPeriodicThread attempts to address these problems by providing a simplified
interface: subclasses need only implement a single pure virtual entry point:
virtual void MLPeriodicThread::DoPeriodicThread() = 0;
And they will start getting cpu time on the next pass through the event loop. No need to Schedule()
or Resume() thread execution from the main thread.
Fixed stupid error in MLDispatcher::DispatchMacNullMessage() which could prevent terminated threads
from being immediately disposed. This error was inadvertently introduced along with SetYieldThrottle().
• 20030715: Added two new pieces of metainformation to albums: "category" and "timeframe". We're going to
need these to properly import albums into the new online database Leo is building. Added more fields/combos
to the Album Options window: title, category, timeframe.
When we're done erasing (formatting) cards, we now provide visual and aural feedback so that the user
can safely remove the media.
Folders generated by the HTML exporter were too cluttered. To reduce the clutter, we now group all
files except "index.html" in a subfolder named "Contents". This is the new directory layout:
html_folder ----+---- index.html
|
+---- Contents ----+---- galleries ----+---- 0_0.html
| |
| +---- 0_1.html
| |
| +---- ...
|
+---- gallery_all_0.html
|
+---- gallery_all_1.html
|
+---- ...
|
+---- manifest.plist
|
+---- pictures ----+---- 00001.jpg
| |
| +---- 00002.jpg
| |
| +---- ...
|
+---- search.html
|
+---- stylesheet.css
• 20030716: Fixed a couple of CSS syntax errors introduced yesterday.
Made new build available on my website:
<http://www.merzwaren.com/bin/digital-emotion-20030716.sit>
• 20030716: In order to make life easier for the XML importer being developed by Ledsoft for
the new Photo Emotion website, photo keyword maps can now optionally be exported in a more
human-readable format, where keywords applied to a photo are represented by a comma-separated
list of indices, rather than a Base64-encoded binary blob. For example, we can now use this form:
<key>keyword_map</key>
<array>
<integer>2</integer>
<string>30</string>
<integer>7</integer>
<string>9,10</string>
</array>
instead of this one:
<key>keyword_map</key>
<array>
<integer>2</integer>
<data>
AAAAiiAAAAAAAAAAAAAAAAAAAAAAAAAA
</data>
<integer>7</integer>
<data>
AAAACgAAAwA=
</data>
</array>
Which form is used can be controlled using the DigitalEmotionPhoto::UseBase64ForKeywords()
class accessor. The index-list form is built on a new MLBitVector accessor, GetAsIndexList().
For the time being, we'll keep on using the Base64 form in albums, but will adopt the index-list
form for manifest files generated during HTML export.
• 20030717: Marco Silvestri experienced some major disk problems while testing recent builds of DE:
in more than one case, his hard drive suffered from a corrupt Master Directory Block (error -60),
it wouldn't mount after a system crash, and he was forced to reformat it. These severe problems
appear to be connected to my file copying code, although I fail to see how. Perhaps he was using
corrupt memory cards and the card driver somehow misbehaved.
In an attempt to shed more light on this riddle, I added code to print various driver-related
pieces of information to the console at volume-mount time, using DriverGestalt.
Moved a bunch of ugly, mac-specific, low-level debugging code to a new file, DigitalEmotionDebugging.cpp.
The MLFile::Copy() call in DigitalEmotionImporter::ImportFile() is now protected by a try block,
so that failing to copy one file does not cancel the whole volume. Ditto for the Delete() call.
MLDispatcher now supports different "redraw policies", which can be chosen at runtime using
MLDispatcher::SetRedrawPolicy(). This is a MacOS-only accessor. When the default policy
(kRedrawPolicyNormal) is enacted, the dispatcher redraws invalid portions of a window when it gets
update events from WaitNextEvent, as usual. Sometimes, too frequent updates starve the application
of null and mouse-moved events -- this used to happen with the old Vitaminic application running
on slow CPUs. In this case, we can opt for kRedrawPolicyDelayed, a policy where update events
are fabricated with CheckUpdate(), queued for later processing, and WNE is called with an event
mask that suppresses update events. BTW, this policy supersedes the old ML_MACOS_DELAYED_UPDATES
compile-time switch. Sometimes, the opposite problem occurs: we can't process update events often
enough, perhaps because multiple threads keep invalidating multiple windows at once, but each
update event can refresh only one window at a time. This happens to be the case with Digital
Emotion. Here's where kRedrawPolicyImmediate comes into play: when this policy is chosen,
with each pass through the event loop, the dispatcher attempts to redraw all invalid windows
at once.
• 20030718: After much pondering, I've come to realize the severe stability problems recently
experienced by Marco Silvestri were caused by an unforeseen interaction between Quickdraw and
the Thread Manager. Quickdraw was not designed to live in a multi-threaded environment.
For one thing, a lot of Quickdraw APIs get their graphics port parameter implicitly through a
global variable -- which I consider to be one of the worst design flaw of this graphics library,
and a source of countless headaches. Digital Emotion tries to use Quickdraw from several threads
simultaneously, but the Thread Manager knows nothing about Quickdraw, and it will not attempt to
save/restore Quickdraw globals when a thread is switched out or back in. So, unless special care
is taken, a thread may yield, be resumed, and find that the current gworld has been changed behind
its back while it was suspended. In an attempt to solve this problem as transparently as possible
for MLMacThread subclasses, I added custom thread switchers that swap Quickdraw globals when a thread
is switched in and out. This effectively means that each thread gets its own, private, Quickdraw
globals. This change alone seems to cure a lot of problems, although I'm still getting sporadic
crashes when running importer and exporter threads simultaneously. Will investigate.
• 20030722: We now call QDError() when UpdateGWorld() or LockPixels() fail, so we can have a better
understanding of *why* those calls fail.
More stability fixes in my Quickdraw code:
1. When the MLUseCanvas stack-based class is used in conjuction with MLOffscreenCanvases, it
calls LockPixels() in the ctor and UnlockPixels() in the dtor. Unfortunately, LockPixels()
and UnlockPixels() can't be nested, so code like the following can be dangerous:
void foo ( MLOffscreenCanvas & inCanvas, const MLRect & inRect )
{
MLUseCanvas usingCanvas ( inCanvas ) ;
void * rawBits = inCanvas . GetBits ( ) ;
inCanvas . DrawRect ( inRect, MLColor :: cWhite ) ;
DoSomethingWithTheBits ( rawBits ) ;
}
Since MLCanvas::DrawRect itself uses MLUseCanvas internally, by the time DrawRect()
returns, the pixmap is unlocked, and rawBits may be a stale pointer.
Fix: add a mPixelLockCount member to MLOffscreenCanvas that keeps track of the number of
nested calls to MLOffscreenCanvas::BeginDrawing(). MLOffscreenCanvas::EndDrawing() will
only unlock the pixels when the lock count drops to zero.
2. Sometimes thread A may yield, and be switched out, before it sets up a gworld for its
own use. In this scenario, my custom thread switcher will save whatever gworld is current
in thread A's local storage. This gworld may have been created by thread B, which may
eventually release it. When this happens, and thread A is switched back in, the thread
switcher will call SetGWorld() on a disposed gworld, and Quickdraw will get very upset.
Fix: add an MLMacThread::GWorldDied() class-wide (static) notification method that immediately
eliminates all references to dead gworlds; call this method from the MLOffscreenCanvas dtor.
Working on the 2nd screen code [entry #1 of v2 specs].
• 20030723: Implemented thumbnail printing [entry #11 of v2 specs]. The thumbnail sheet is printed
in portrait orientation and holds 24 numbered thumbnails by default (6 rows, 4 columns). The row
and column counts can be changed setting the appropriate keys in the preferences file. A header
at the top of the sheet shows the album title, description, location and time frame (in a single
line).
Change in print queue management: previously, print jobs would be removed from the print queue as
soon as a printer became available. Now, print jobs are removed from the queue only after the
corresponding spool file has been uploaded to the printer and the print command issued. This
means that the relative entry in the Print Queue window doesn't go away until Digital Emotion
is really finished with the job. DigitalEmotionAlbum::UnqueueNextPrintJob() was renamed
DigitalEmotionAlbum::GetNextPrintJob().
• 20030808: It is now possible to open an album by double-clicking the manifest.plist file within.
Implemented the Mode menu. Added "New Album..." and "Open Album..." commands to the File menu, so
now you can switch albums without quitting the application. Added a Working Set menu with commands
to manage the working set, and to switch between working sets:
Clean
Find
-----
Sort by Import Date
Sort by Shoot Date
-----
Previous Working Set
Next Working Set
The sort commands are new and are implemented by a new DigitalEmotionAlbum::SortWorkingSet()
method, which takes a DigitalEmotionAlbum::ESortKey parameter. Currently, the only supported
sort keys are import date (the default) and shoot date. More keys may be added in the future.
This feature was not in the official wish list, but I find it useful for my own albums.
MLDispatcher fix: accept update events even when the redraw policy is kRedrawPolicyImmediate, or
windows won't get redrawn while the process is in the background.
• 20030818: Started working on the Keyword Editor. This is a new facility that will allow
addition, deletion, renaming and reordering of keywords and keyword families associated with
the current album, using a simple, but powerful, drag-and-drop interface.
• 20030819: Added a new mWASTELibVersion field to SystemInfo.
MLDate::GetDateString() and MLDate::GetTimeString() should return MLUniString's, not MLString's,
since those are locale-dependent strings that may contain non-ASCII characters, and we should
try to limit the use of MLString's in the framework to ASCII-only strings (and eventually phase
them out altogether in favor of MLUniString's).
HTML exporter bug fix: generated index files could be corrupt if date strings contained non-ASCII
characters.
Major overhaul to MLListView so that it now uses UTF-16 strings internally. This puts MLListView
on par with the majority of visual classes dealing with user-visible text. In particular,
MLListView::SetListText() now takes a MLUniString. The sorting function used by MLListView is now
MLUniString::CaselessCompare() rather than the mac-specific macstrcmp() (which was based on
CompareText). This will make the Windows port easier.
MLDrag now has specialized methods for working with UTF-16 strings: HasUniStringFlavor(),
GetUniStringFlavor(), AddUniStringFlavor() and SetUniStringFlavor(). MLDrag::HasMacFlavor()
takes an additional, optional output parameter (bool * outIsPromised) that is set to true
if the specified flavor has been promised by the sender, but not actually delivered yet.
This feature relies on GetFlavorFlags() setting the flavorDataPromised bit correctly, so it
only works on Mac OS X 10.1 or newer (see <http://developer.apple.com/technotes/tn/tn2029.html#DRAGMGR>).
• 20030821: MLBitVector revision. Fixed bug in MLBitVector::Resize(), added MLBitVector::Insert(bool, UInt32)
and MLBitVector::RemoveAt(UInt32). The new insertion/removal methods are used by
DigitalEmotionPhoto::MakeRoomForNewKeyword(), DigitalEmotionPhoto::MoveKeyword() and
DigitalEmotionPhoto::RemoveKeyword() to keep keyword maps in sync with changes made through
the Keyword Editor.
• 20030822: Finishing touches to the Keyword Editor. You can now invoke the Keyword Editor from
the Keyword window displayed after a photo volume has been imported, to add familes and keywords
on the fly. I think this fully implements [entry #4 of v2 specs]. As for [entry #8 of v2 specs]
("ability to choose which Common Keyword families to use when a new album is created"), I think
the Keyword Editor makes this item obsolete, as it's now very easy to delete any family at any
time, and all Common Keywords are automatically added to every new album.
Some more work on activation codes. Added a DigitalEmotionApp::VerifyActivationCode() placeholder
which will parse the current activation code (a string saved in the preferences under the key "activation_code"),
make sure it is consistent with the SystemInfo structure collected at startup, and extract an
expiration date from the activation code, which is then passed to the existing CheckExpirationDate().
Implemented registration window and wired its code to the existing (but so far unused) HTTP code.
• 20030824: MLInternetConfig revision. Several Get accessors now return default values if the corresponding
preference key is not present in the Internet Config database. Previously, they would throw OS
exceptions (-666 == icPrefNotFoundErr), and this caused my registration code to fail miserably on
machines where the kICOrganization key was missing. The accessors in question are:
GetRealName default value: empty string
GetOrganization default value: empty string
GetMailAddress default value: empty string
GetMailAccount default value: empty string
GetMailPassword default value: empty string
GetDownloadFolder default value: desktop folder
• 20030825: "TN" was missing from the Italian province list, for some reason.
• 20030826: Revised DigitalEmotionApp::SendRegistration() so that it supports HTTP redirection (3xx status codes).
This allows me to have DE send its first registration request to a very stable Apache server I control, which
will redirect the query to a PC running IIS, hosted by Ledsoft.
Added MLBufferStream::Rewind() and MLBufferStream::Reset(). The first function, meant for input streams, rewinds
the position of the stream to offset zero; the second function, meant for output streams, rewinds the stream
and also clears the buffer.
• 20030827: Working on the remote activation algorithm.
• 20030828: Again on the remote activation algorithm. Experimented with HTTP compression.
• 20030830: The 2nd screen now works in 4/9/16 mode. This completes [entry #1 of v2 specs].
Started work on the last missing entry of the v2 specs, i.e., #6: export of master files for backup
purposes.
• 20030831: Finished work on the export feature. This basically completes version 2 of Digital Emotion.
Added a new boolean preference key (kKeyPreferencesSlideShowUseMaster == "slide_show_use_master") that
determines whether masters or scaled-down pictures are used to compose canvases used for the slide show.
The default value is false, since slide shows are going to be projected on a low-resolution (640x480)
second monitor most of the time. Setting this preference to true (and kKeyPreferencesSlideShowInterpolationType
to kInterpolationBicubic == 2) provides the maximum possible preview quality, but at the expense of a
significant slowdown.
Removed superfluous close box from HTML progress window.
• 20030909: Marco Silvestri filed a list of 9 bugs/enhancement requests. Most are minor quirks, easily fixed.
A few of them, though, will require some effort. Here's the complete list:
1. Make lists auto-scrollable, i.e., make it possible to drag items to out-of-view portions of lists.
2. Once the master exporter is used, the EXPORT button remains inactive.
3. Find All should deselect all.
4. When the layout mode is not PHOTO, the activation state of some buttons may go out of sync.
5. Newly created layouts should not be automatically selected.
6. Make more room for photo labels in the 16-photo viewer. Labels above 999 don't fit.
7. Exception when the printer is out of ink.
8. Switching to the Finder (with cmd-H) and back doesn't hide the menu bar in OS 9.
9. Execute "manual" import in a separate thread, just like automatic import.
• 20030911: Addressed entries #2, #3, #4, #5 and #6 of [20030909 wish list].
Refactored the class hierarchy by inserting an extra base class DigitalEmotionExporter between
MLPeriodicThread and the two concrete exporter classes (DigitalEmotionMasterExporter, DigitalEmotionHTMLExporter).
The Stop button in the Export Progress window now works with the master exporter as well.
• 20030913: "Manual" imports are now executed in their own separate thread, rather than in the main thread.
The real work is now performed by a new DigitalEmotionManualImporter class (an MLPeriodicThread).
Since this class has a lot of code in common with the existing DigitalEmotionImporter class, the common
code was factored out to a new DigitalEmotionImporterBase from which both classes inherit.
This addresses [entry #9 of 20030909 wish list].
• 20030914: The HTML exporter will no longer add title attributes to img tags for photos that don't have
a valid shoot date.
• 20031001: Moved a bunch of code to get info about the hardware, OS, etc. to a new "MLMacSystemInfo.cpp" file.
Added new preference keys to control the fonts used in various UI widgets:
font_edit_fields [string]
font_lists [string]
font_popup_menus [string]
font_size_edit_fields [integer]
font_size_lists [integer]
font_size_popup_menus [integer]
The first three keys specify font names, or lists of font names, separated by commas, e.g.:
<key>font_lists</key>
<string>Lucida Grande, Geneva</string>
Digital Emotion will scan the list and pick the first available font.
The empty string stands for the so-called "application" font (usually Geneva on OS 9, Lucida Grande on OS X).
The special name "$SYSTEM" stands for the system font (usually Charcoal on OS 9, Lucida Grande on OS X).
The "font_size_*" keys specify the corresponding point sizes. Specifying a zero or negative size instructs
DE to use the default font size (as returned by the Carbon API GetDefFontSize()).
• 20031002: New build posted to my web site:
<http://www.merzwaren.com/bin/digital-emotion-20031002.sit>
• 20031016: Phone conversation with Marco Silvestri. There seems to be a hard-to-reproduce, sporadic problem
with keywords sometimes getting mixed up for no apparent reason. I'll investigate it, but I'd rather have
a reproducible test case to work on. More wish list items:
+ The screen position of all windows (including Find, Set Keywords, Keyword Editor) should be persistent.
+ The Set Keywords window currently doesn't offer a way to inspect *all* the keywords associated with a
picture -- it shows just the first keyword for each family. Devise a way to show all the keywords.
+ Add a way to jump to a picture given its serial number. Better yet, add a way to define a working
set by entering the serial numbers of its pictures, separated by commas or in range form. For example,
entering "1-4, 12, 18-20" should create a working set made of pictures number 1, 2, 3, 4, 12, 18, 19 and 20.
+ Shadow text.
+ Picture selection rings should appear on the second (customer) monitor as well.
+ The Print Queue window should report the picture serial number(s) corresponding to each job.
• 20031017: Started working again on the Windows port. I merged the old Win32 branch we forked off in May
back into the main codebase. We now have a single project with PowerPC and x86 targets. The x86 target
compiles and links, but I had to comment out a bunch of stuff, so I don't think the executable can take
off at this stage.
I rewrote the entire DigitalEmotionScanner hierarchy so that it uses x-platform types (MLPath, MLUniString)
instead of mac-specific ones (FSRef, HFSUniStr255). The base class is somewhat less flexible than
before, but we'll gain Windows compatibility (almost) for free. TODO: test these changes.
In various places I was using the MLFont(FMFontFamily, UInt16, UInt16) ctor, which is mac-only, to
create fonts from the default system or application typefaces. I replaced those ctors with
MLFont(const char *, UInt16, UInt16), which is x-platform, and modified the MLFont implementation so
that it special-cases the font names "$SYSTEM" and "$APPLICATION" and maps them to the corresponding
system and application fonts. This special-casing needs to be done on the Win32 side as well.
Added hand-made implementations for missing, non-standard strcpyupr() and strncpyupr() functions (why
were these used in the first place?)
• 20031019: Added libpng 1.2.5 and zlib 1.1.4 to the project, and #defined ML_PNG_NATIVE_SUPPORT to 1 in
the prefix files. This causes MLOffscreenCanvas to rely on libpng (rather than QuickTime) to load PNG
files.
<http://www.libpng.org/pub/png/libpng.html>
<http://www.gzip.org/zlib/>
• 20031020: Working on the Windows port. Added some missing MLDate member implementations (GetDateString()
and GetTimeString()). Fixed some problems with the Win32 implementation of MLFont. Changed MLJpegDecompressor
so that it uses 32-bit deep surfaces on Win32 as well (it used to create 24-bit surfaces -- no alpha channel).
This is needed so that interpolation code in MLEffect.cpp can work without changes.
Added an "x86.final" target to the project that compiles with full optimizations on.
• 20031022: More work on the Windows port.
• 20031030: Rewritten the Win32 implementation of MLCanvas::DrawRect() so that it more closely mimics the
mac semantics. In particular, DrawRect() now honors the pen width set with MLUsePen -- previously, it
would call the GDI API FrameRect(), which always draws a one-pixel thick outline.
Incorporated new MLSystemInfo class from Leonardo. This is a x-platform reimplementation of MLMacSystemInfo.
Dug out the old MLWin32View class, a convenient adapter that allows Windows controls (buttons, edit boxes,
scrollbars, sliders, progress bars, etc.) to be used as regular views. A MLWin32View is both an MLView and
an MLWindowProcHandler. Incorporated MLWinEditBox, a subclass of MLWin32View. Added a new MLWinSlider class,
also a subclass of MLWin32View.
• 20031031: Leonardo renamed MLWin32View to MLWinControl (to make its relationship with MLMacControl more obvious)
and added more subclasses: MLWinScrollbar, MLWinTab and MLWinProgressBar. I added MLWinButton, modeled after
MLMacButton. In several of these controls, the underlying child HWND sends various notification messages
(WM_COMMAND, WM_NOTIFY, WM_HSCROLL, etc.) to its parent window, which are handled by MLWindow::WindowProc().
MLWindow::WindowProc(), in turn, retrieves a pointer to the C++ wrapper object around the child HWND, and
redirects the notification to it, suitably converted into a virtual member call (MLWinControl::HandleCommand(),
MLWinControl::HandleNotification(), MLWinControl::HandleScroll(), etc.). The concrete control subclasses
can then override the relevant methods, and behave like their Macintosh counterparts, by broadcasting
a kCommandSlider command (MLWinSlider), performing an MLAction (MLWinButton), what have you. This strategy seems
to work pretty neatly.
• 20031103: The MLWinThread class is now on par with its Macintosh counterpart -- it mimics the semantics of
Thread Manager cooperative threads by using Windows fibers with a simple round-robin scheduler.
Threads can be "stopped" (made ineligible for scheduling) by calling MLWinThread::Unschedule(), or resumed
to their "running" status by calling MLWinThread::Schedule(). Of course, thread switching can only happen
when MLWinThread::Yield() is explicitly called by the current thread. Just like in the Macintosh implementation,
a private MLWinThread ctor is used to create a wrapper around the main application thread -- a singleton which
is created before main() is entered, and never destroyed. This ctor calls ConvertThreadToFiber(), thus making
it possible for the regular ctor to call CreateFiber().
MLWinThread.[h/cpp] defines a thread/periodical hybrid, MLPeriodicThread, which is almost identical to the
Macintosh implementation. I'll merge MLMacThread.[h/cpp] and MLWinThread.[h/cpp] as soon as I have a chance.
Bug fix: MLOffscreenCanvas::ConstructFromPNGStream() would incorrectly call png_set_background() when reading
images with an alpha channel into a 32-bit deep surface.
Bug fix: the Win32 implementation MLFile::Rename() was badly broken -- it would rename files *and* move them
to the current directory.
Fixed endian issues in MLBitVector::MLBitVector(const MLBuffer&) and MLBitVector::GetAsBuffer().
• 20031104: Fixed fiber termination issue in MLWinThread. If a fiber proc terminates normally (i.e., if
MLWinThread::CommonEntryPoint() returns), Windows will terminate the thread that owns the fiber. Since
our implementation spins all fibers off the main thread, this means exiting a fiber proc would immediately
kill the process. So we now destroy fibers from the main fiber and the MLWinThread destructor (only
called in the context of the main fiber) the now calls DeleteFiber().
MLPeriodical now has a new virtual method Terminate(), invoked by the dispatcher when the periodical has
stopped running and has just been removed from the dispatcher list. The default implementation does nothing.
MLPeriodicThread overrides Terminate() to delete itself, which destroys the underlying fiber.
Worked around a tricky problem with MLPath::EnforceInvariants() and volumes that have just been mounted.
MLPath::EnforceInvariants() relied on the dwAttributes field of the SHFILEINFO structure filled in by SHGetFileInfo()
to determine whether a given pathname referred to a directory or not. Unfortunately, SHGetFileInfo() would sometimes
fail for files living on recently mounted volumes not yet detected by the shell. We now use GetFileAttributes()
exclusively to determine whether a given path refers to a directory. We only use SHGetFileInfo() to detect
shortcuts (symbolic links).
• 20031106: More tweaks to MLWinThread.
Fixed broken Win32 implementation of MLFile::Copy() (the second parameter to CopyFile() must be the
pathname of the destination *file*, not the destination directory).
Until we incorporate code to read TIFF files (libtiff?), the Windows version can only import JPEGs -- various
places in the code where we accept TIFF files (such as DigitalEmotionImporterBase::IsGraphicsFile() and
DigitalEmotionApp::ImportPictures()) must be protected by #if ML_USE_QUICKTIME guards.
<http://www.libtiff.org/>
DigitalEmotionPhoto::MakeThumbnail() can now use an alternate "high-quality scaling" codepath, where the
source canvas is scaled down using our built-in bicubic interpolation algorithm. The old codepath relies on
MLCanvas::Blit(), implemented on top of the system-provided pixel-scaling API: CopyBits (MacOS/Quickdraw)
or StretchBlt (Win32/GDI). Unfortunately, while CopyBits() yields passable results, StretchBlt() does a
really lousy job of downscaling.
Digital Emotion would fail to load an existing album if it couldn't recreate missing thumbnails (if any) for all
photos. Now it will just silently skip photos for which thumbnails can't be re-generated. This can happen on
Windows when importing an album created on the mac, containing TIFF masters whose corresponding thumbnails
have been deliberately removed.
• 20031107: Fixed endianness issue in DigitalEmotionPrintJob::CreateSpoolFile().
TODO: Provide Win32 implementation of MLDispatcher::GetCurrentModifiers().
Modified MLMacPopupMenu so that it can create and manage its own MLMenu, rather than relying on client code
to attach a separately created MLMenu object to it. The rationale for this change is to make it easier to
adapt the existing initialization code to the Windows implementation, which will likely use drop-down list
controls to mimic mac popup menu controls.
Incorporated initial version of MLWinDropDownList class from Leonardo.
• 20031110: Added MLFont::ListFonts(); Win32 implementation of MLDispatcher::GetCurrentModifiers().
Explicit CFRelease() calls in various places have been replaced by implicit calls to
AutoHandle<CFString>::~AutoHandle<CFString>().
• 20031111: Better handling of libpng error and warnings: instead of setting up a jmp_buf structure for
libpng to call jongjmp() with, we now install a custom error callback which throws a C++ exception
with an informative message. We also install a custom warning callback which writes warning messages to
the console in debug builds.
MLOffscreenCanvas::ConstructFromPNGStream() would refuse to load PNG files with bit depths other than 8,
for no good reason at all (since libpng handles all expansion details). This would prevent the Album Options
window from loading correctly, since one of its combo buttons references a 1 bit-deep PNG (triangle.png).
• 20031112: (Win32) MLSystemInfo::GetEthernetAddress() no longer throws an exception on a machine lacking an
Ethernet card.
(Win32) MLFont::ListFonts() now sorts the returned list alphabetically, and only includes TrueType ANSI fonts.
(Win32) The MLWinDropDownList control now includes a vertical scrollbar if the items don't fit a fixed (200 pixels)
height. Some cosmetic issues were fixed as well. Added a new MLWinControl::ResizeControl() accessor that changes
the bounds of the underlying HWND without affecting the MLView bounds. This is needed for controls such as
MLWinDropDownList where the two rectangles don't match.
(Win32) The Find and Print Options windows now work.
• 20031113: ML Framework change: when a MLMacPopupMenu broadcasts a kCommandPopupChanged message to notify its listeners
that a new menu item has been selected, the new item index is now zero-based rather than one-based, for consistency with
the various MLMenu accessors and with the general rule used in the framework that sets of N items are numbered
0 to (N - 1). Also, to get and set the current selection of a MLMacPopupMenu object, it is now recommended to use
GetSelection() and SetSelection(), instead of the lower-level GetValue()/SetValue() inherited from the MLMacControl
base class. GetSelection()/SetSelection() are zero-based, whereas GetValue()/SetValue() are one-based, according
to the Macintosh Control Manager convention.
MLMacPopupMenu no longer exposes its private MLMenu object: it is no longer possible to obtain a reference to it
through GetMenu(), nor to specify an existing MLMenu to the MLMacPopupMenu ctor.
MLMacControl now supports the cloning mechanism implemented by several other MLView subclasses: calling Clone()
on a MLMacControl makes a more or less identical copy of the object.
Renamed GetValue()/SetValue() accessors in MLWinDropDownList to GetSelection()/SetSelection() and made them zero-based.
• 20031119: Reworked the InterfaceLib shim I use to trick the Sony support library for the UP-D70 printer to work in a
Carbon application. The new shim has a new entry point (SetSCSIActionHook) that allows Digital Emotion to install a
hook to monitor calls to SCSIAction() made by the Sony library. #Defining TRACE_SCSI_CALLS to 1 causes Digital Emotion
to install a SCSIAction hook (TracingSCSIAction) that logs calls to the debug console. The purpose of this machinery
is, of course, to reverse-engineer the Sony library in order to write a compatible Win32 implementation.
• 20031125: MLUniString::InitFromUTF8String() now supports characters beyond the BMP (Basic Multilingual Plane) and is
stricter about malformed UTF-8 sequences.
• 20031127: Started working on a Win32 reimplementation of the Sony "UPLib" library that allows communication with the
Sony printer via SCSI. This reimplementation will be based on the Win32 ASPI (Advanced SCSI2 Programming Interface)
layer, originally developed by Adaptec, maker of the popular PowerDomain 2930 card I've been using to connect my test unit.
<http://www.hochfeiler.it/alvise/ASPI_1.HTM>
<http://www.ncf.carleton.ca/~aa571/aspi.htm>
<http://www.adaptec.com/worldwide/product/proddetail.html?sess=no&language=English+US&cat=%2fProducts%2fAPD-2930U&prodkey=APD-2930U>
A new class, tentatively named ASPI (will rename to MLWinASPI soon), provides a convenient C++ wrapper around WNASPI32.DLL.
Another class written on top of this (UPImpl) provides the low-level implementation of several calls in "UPLib".
Finally, the relevant UPLib C entry points have been reimplemented on Windows through the UPImpl class.
The official SCSI2 specification can be found here:
<http://www.micro-magic.com/ftp/scsi2.pdf>
• 20031130: Incorporated several changes from Leo, including a new MLWinDateTimePicker class that provides a Win32 counterpart
to MLMacClockControl. The kModifiersChanged message is now implemented for Win32 as well, but only for the Alt key.
• 20031202 (my birthday): A more accurate reverse-engineering of the Sony communication protocol, thanks to a revised
TracingSCSIAction() function, along with a somewhat better understanding of the SCSI2 standard, allowed me to correct
several errors in the layout of CDBs (Command Descriptor Blocks) sent to the printer at various steps of the printing
process. I'm happy to announce I was finally able to print the very first test page from the Windows version of Digital
Emotion running on Windows XP. For what it's worth, it was a reproduction of Francis Bacon's "Study after Velazquez's
Portrait of Pope Innocent X".
Renamed the ASPI C++ wrapper to MLWinASPI and moved it into its own files (MLWinASPI.[h/cpp]).
The problem with ASPI is that 1. It is no longer supported by Microsoft on NT-class operating systems; and 2. there
are a zillion different implementations around. The one we're using on our test PC is the latest version from
Adaptec (version 4.72). This appears to work seamlessly with the Adaptec PowerDomain 2930 PCI card I borrowed from
my PowerMac G4, but unfortunately, it doesn't seem to work as smoothly with Leo's Tecram 390 PCI card, and doesn't
work at all with RATOC Systems FR1SX FireWire-to-UltraSCSI converter.
<http://www.ratocsystems.com/english/products/subpages/firerex1.html>
I fully expect this ASPI implementation to be incompatible with USB-to-SCSI converters as well, making it unsuitable
to drive Sony printers from a modern portable machine (which is likely to feature IEEE 1394 or USB 2.0 ports, but
no SCSI connectors). The solution to this problem appears to involve a make-vs-buy decision: either 1. we write an
alternate implementation based on Windows XP's native SCSI layer (SPTI = SCSI Pass-Through Interface), or 2. we buy
one of the commercially available SPTI-based ASPI layers, such as the ones marketed by Nexitech or RecDev:
<http://www.nexitech.com/products.htm>
<http://www.recdev.de/addon.html#aspi>
There are a number of interesting Usenet threads on ASPI/SPTI issues, like this:
<http://groups.google.com/groups?selm=bgtnu1%24em8%241%40slb9.atl.mindspring.net>
• 20031204: Added a new MLDate::GetCompileDate() static accessor, which returns the date/time when the function was
compiled (obtained by parsing the ANSI C __DATE__ and __TIME__ macros). Digital Emotion will now refuse to run if
the current time is less than the compile time. This makes setting back the system clock to prevent Digital Emotion
from expiring a little bit harder. Fixed a bug in the Win32 implementation of MLDate::GetCurrentDate(): this accessor
should return the current *local* time, not the current UTC time. Changed the Win32 implementation of MLDate::LocalToUTC()
and MLDate::UTCToLocal() so that it doesn't depend on TzSpecificLocalTimeToSystemTime/SystemToToTzSpecificLocalTime,
since these are not available on all Windows systems. Instead, we just add/subtract the Bias field of the TIME_ZONE_INFORMATION
record returned by GetTimeZoneInformation.
Galleries created by the HTML exporter now have Previous/Next buttons both at the top and at the bottom of the gallery.
• 20031205: Updated zlib to version 1.2.1, announced today by Mark Adler.
• 20031209: Experimented with a test version of the NexiTech ASPI emulation layer, made available by Don Matthews for
evaluation purposes. This ASPI implementation works beautifully with the Ratoc adapter, and with the Tecram PCI card
as well, although in the latter case I had to lower the SCSI transfer buffer size from 64K (which I think is the
theoretical maximum for the UP_CMD_SEND_IMAGE_DATA command) to just 24K.
Added a couple of new keys to the preferences file: "scsi_transfer_buffer_size" (an integer specifying the maximum
size of data chunks pumped through the SCSI bus, in KB, min: 1, max: 64, default: 64), and "aspi_layer_name" (the
name of the ASPI DLL to load, default: "wnaspint").
• 20031215: Minor optimizations to MLString::Replace() and MLUniString::Replace().
• 20031217: New albums wouldn't inherit Common Keywords in some cases. This was a thread synchronization issue.
Ported the TCP/IP code to Windows. Added MLWinSocket.[cpp|h] and ws2_32.dll to the project. Slight tweaks to
MLHTTPRequest.[cpp|h] to make it cross-platform.
• 20031218: Fixed long-standing, subtle bug involving negative numbers in MLString::IntToString() and MLUniString::IntToString().
Fixed broken Win32 implementation of MLSystemInfo::GetOSName(). Win32 implementation of MLSystemInfo::GetRAMSize() now
uses GlobalMemoryStatusEx() instead of GlobalMemoryStatus() -- this allows us to correctly recognize systems with more than
4 GB of RAM. Fixed bug with redirects of HTTP GET requests in DigitalEmotionApp::SendRegistration().
The remote activation code now works correctly on Windows, but should nonetheless be modified so that we have alternate
ways to uniquely identify PCs that don't have an Ethernet card.
• 20031225: Made HTML exporter more flexible by adding several accessors to fine-tune the quality of the generated thumbnails.
Almost all of the new accessors have a corresponding preference key. Here's a table of new keys, their meanings, and their
default values:
+-----------------------------+--------------------------------------------+------------------+
| key | meaning | default value |
+-----------------------------+--------------------------------------------+------------------+
| html_use_masters | generate HTML thumbnails from | false |
| | masters (true) or from working | |
| | images (false)? | |
+-----------------------------+--------------------------------------------+------------------+
| html_apply_layouts | include overlays in HTML thumbnails? | false |
+-----------------------------+--------------------------------------------+------------------+
| html_high_quality_scaling | use high-quality bicubic interpolation | true on Win32 |
| | to scale down source images? | false on Mac |
+-----------------------------+--------------------------------------------+------------------+
| html_jpeg_quality | JPEG compression quality of thumbnails | 0.75 |
| | (valid range is 0.0 to 1.0) | |
+-----------------------------+--------------------------------------------+------------------+
"html_use_masters" should be set to true when generating relatively large HTML thumbnails (say, 400 pixel tall/wide or more).
Doing so slows down the generation process, of course.
"html_high_quality_scaling" is true by default on Win32 because the system-provided scaling algorithm is pretty lousy.
Corrected a bug in the MLJpegCompressor class: SetQuality() had no effect, since DoExport() would reset it to its
default value (0.75) anyway. This happened because we would call jpeg_set_defaults() in DoExport() -- now we call
jpeg_set_defaults() in the ctor instead.
Experimented a bit with the "optimize_coding" option of the IJG compressor object. Turning this option on (it's off by default)
causes the JPEG compressor to calculate optimal Huffman tables for the source image, instead of using default tables.
This will reduce the JPEG file size slightly without affecting its quality, at the expense of an extra pass (slower compression).
According to a simple test I've performed (compressing 77 1024x768 pixel real-world pictures shot by my Sanyo camera down to
400x300 pixel thumbnails), the average file size reduction is just 3.5%, ranging from about 2.25% to 6%. Probably not enough
to justify the extra time.
Fixed a bug where MLMessage::IsFunctionKey() did not recognize arrow keys on the Macintosh.
• 20031230: The warning dialog that appears when a build is about to expire now offers the option to connect to the activation
server, to check whether a renewed license is available. The serial number must be reentered anyway.
Made the activation code stored in the preferences.plist file somewhat more resilient to tampering.
• 20040101: New Macintosh build posted to my web site:
<http://www.merzwaren.com/bin/digital-emotion-mac-20040101.sit>
• 20040105: On Mac OS X, MLSystemInfo::GetRAMSize() now attempts to determine the available physical RAM using the
gestaltPhysicalRAMSizeInMegabytes Gestalt selector. This allows us to get correct results on machines with more
than 4 GB of RAM.
• 20040107: Implemented an auto-update function. When Digital Emotion connects to the activation server to obtain or
renew a license, it's now returned the version and the URL of the most recent public build. If a newer version is
available, Digital Emotion offers to download it. If the user clicks OK, a DigitalEmotionDownloader thread is spawned
which proceeds to download the update archive to a temporary folder. When the download terminates successfully, the
archive is moved to the desktop and a post-processing application is invoked to decompress it (the post-processing
part is currently only implemented on the Macintosh).
The XML document returned by the activation server includes two new keys ("current_version" and "current_version_url")
and looks like this:
<?xml version='1.0' encoding='utf-8' ?>
<plist>
<dict>
<key>activation_code</key>
<string>1864BA5F-53D3BD3D-12E92D40-C73F37B5-52F9DD85</string>
<key>message</key>
<string>Product successfully activated.</string>
<key>current_version</key>
<string>0x01206003</string> <!-- 1.2b3 -->
<key>current_version_url</key>
<string>http://www.merzwaren.com/bin/digital-emotion-mac-20040101.sit</string>
</dict>
</plist>
• 20040114: Following changes made to the code that prepares print canvases (see entry 20030624 of this log), the
Orientation parameter to DigitalEmotionPrintJob::CreateSpoolFile() (and DigitalEmotionAlbum::CreatePrintJob()) is always
kOrientationLandscape, so it's redundant. I'm removing this parameter and the unused code path that it controlled.
• 20040114: Fixed a long-standing, crashing bug sometimes observed when quitting Digital Emotion with a non-empty print
queue. The problem was that the DigitalEmotionPrintJobManager thread would sometimes terminate prematurely because of
an uncaught exception, and the current album would keep a stale reference to the thread, which it would attempt to use
in the dtor. The job manager thread now calls DigitalEmotionAlbum::JobManagerDied() before exiting.
• 20040114: Framework change: the MacOS implementation of MLWindow::GetFrontWindow() now uses ActiveNonFloatingWindow()
(Carbon targets) or FrontNonFloatingWindow() (classic targets) instead of FrontWindow(), following a recommendation by
Eric Schlegel on the Carbon mailing list. FrontWindow() will likely return the menu bar WindowRef in the next major
revision of Mac OS X (10.4?), which is not what we want.
• 20040116: Overhauled the printing code in order to support printing through standard Windows printer drivers.
I introduced two new abstract base classes, DigitalEmotionPrinterClass and DigitalEmotionPrinter. DigitalEmotionPrinter
represents a single printing device. DigitalEmotionPrinterClass is a factory class representing a family of similar
or identical devices, it has a LookForPrinters() method which can be used to discover devices, and CountPrinters()/GetNthPrinter()
methods which can be used to enumerate discovered devices.
The existing SonyUPD70A_Printer and SonyUPD70A_PrinterManager classes are now concrete implementations of DigitalEmotionPrinter
and DigitalEmotionPrinterClass, respectively.
Two new classes, WinPrinter and WinPrinterManager, are concrete implementations of the same abstract bases which
provide convenient wrappers around the Win32/GDI printing model, or the subset needed by Digital Emotion to print
bitmap pictures.
This is just a first attempt at unifying two rather different printing models, the original one supported by Digital
Emotion for Macintosh, and the native Windows printing model. It's still far from perfect. In particular, I haven't
yet addressed the issues of managing a unified print queue and job logging in the GDI implementation. OTOH, I've been
pressured to provide a version of Digital Emotion for Windows that can print to Kodak printers for which we haven't
developed a custom module yet.
• 20040121: Implemented About Box using the HIAboutBox API (Mac OS X Jaguar only).
MLSystemInfo::GetClockSpeed() will now use the gestaltProcClkSpeedMHz selector on Mac OS X, if available.
• 20040129: MLDispatcher::PostMessage() used to be implemented in two different ways on Mac and Windows. On Mac, it
used a private message queue (MLQueue<MLMessage>) where custom messages would be enqueued and later dequeued by
MLDispatcher::GetMessage(). On Windows, it would post a Windows custom message to the main thread using the Windows
API PostThreadMessage(). Unfortunately, this disparity of implementation led to subtle discrepancies in behavior:
for instance, MLApplication::MessageBox() would silenty discard queued messages on Windows (but not on Mac).
This side effect is documented, BTW:
<http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/WinUI/WindowsUserInterface/Windowing/MessagesandMessageQueues/MessagesandMessageQueuesReference/MessagesandMessageQueuesFunctions/PostThreadMessage.asp>
To avoid this problem, we now use a completely uniform implementation on both platforms, based on the Mac code
(private message queue).
Incorporated a completely rewritten implementation of the Win32 code that generates MLMessage :: kVolumeMounted messages,
courtesy of Leo. The new implementation is based on SHCNE_MEDIAINSERTED shell notifications.
[Win32] Added MLDll::GetFunctionByOrdinal(), and MLDll::GetFunctionByOrdinalNoThrow() functions. These allow loading
of unnamed functions from a DLL. It turns out we needed this functionality to load SHChangeNotifyRegister() from older
versions of SHELL32.DLL, when this function was undocumented and unnamed.
• 20040204: Implemented a "manual registration" feature. This is meant to be a fall-back mechanism to register Digital
Emotion on machines that don't have an Internet connection, not even sporadic. Clicking the "Register Manually" button
on the Registration window will display a longish (49 characters) "manual registration" string that encodes the most
relevant information that would be sent to the activation server via HTTP if an Internet connection were available.
This manual registration string can be then communicated by non-Internet means (e.g., phone or fax) to an activation
center which will hand back an activation code for the product.
Fixed a cosmetic bug in MLListView on Windows. This turned out to be caused by a bogus clipping region. Setting the
clipping region to a reasonable value at window creation time cured the problem.
• 20040205: Reworked MLWinEditBox so that it behaves more like its Macintosh counterpart (MLMacText). In particular,
MLWinEditBox now implements SetEditFieldBehavior() and AttachFocusRing(), it sends kCommandSubmitField when appropriate,
and it reacts correctly to DoFocusAcquired() and DoFocusLost() notifications.
• 20040206: More fixes to MLWinEditBox. First half-decent implementation of the MLWinScrollbar class -- previously, this
class was little more than a placeholder; it didn't even inherit from MLAbsScrollBar as it should, so it didn't work at
all in conjuction with panoramas. Correctly implementing MLWinScrollbar::SetProportion() for Win32 scrollbars turned
out to be harder than it appeared at first. The MLAbsScrollBar::SetProportion(UInt32 inVisibleSize, UInt32 inTotalSize)
function is meant to be used to support system-defined proportional scroll bars. Its Macintosh implementation is based
on the SetControlViewSize() API. Its initial Windows implementation, based on the SetScrollInfo() API called with the
SIF_PAGE selector, didn't work quite right. The problem turned out to be that, in the case of proportional scrollbars,
Windows assumes the scrollable range (max - min) to be the whole height (or width, in case of horizontal scroll bars) of
the scrollable view, while MLPanorama sets the scrollable range to the total height *minus* the visible height, which
seems to make more sense to me. The weird thing is that if I stick to non-proportional scrollbars (i.e., if I never
use the SIF_PAGE selector), my idea of what the scrollable range should be seems to work just fine.
• 20040209: The MLMacComboBox class now works on Windows, too, and has been renamed to MLComboBox. It's basically unchanged
except, on the Windows side, we use MLWinEditBox instead of MLMacText for the edit field. I do resort to a very ugly
kludge to make sure the combo box list (which is just an MLView, not an HWND) doesn't get overdrawn by edit fields behind
it in z-order. Like its Macintosh counterpart, MLWinEditBox now broadcasts kCommandTextChanged messages when the user types
into it -- this is required for MLComboBox auto-completion to work correctly. As a result of all these changes, the Album
Options window now works correctly on Windows, too.
Implemented the color picker dialog on Windows. This is the dialog that comes up when you double-click a color well in
the Text palette, allowing you to change one of the 13 predefined colors.
• 20040224: Began integrating the latest version of libtiff (3.6.1) into the ML framework.
<http://www.libtiff.org/>
The following macros are #defined in prefix files to cause the corresponding codecs to be compiled in:
CCITT_SUPPORT CCITT Group 3 and 4 (compression codes 2, 3, 4, 32771)
PACKBITS_SUPPORT Macintosh PackBits (compression code 32773)
LZW_SUPPORT Lempel-Ziv & Welch (compression code 5)
THUNDER_SUPPORT 4-bit RLE scheme from ThunderScan (compression code 32809)
NEXT_SUPPORT 2-bit scheme (compression code 32766)
LOGLUV_SUPPORT LogLuv
JPEG_SUPPORT JPEG-in-TIFF (compression code 7)
ZIP_SUPPORT experimental Deflate scheme (compression code 32946)
Introduced a new MLTiff class as an interface between libtiff and the ML framework.
A new compile-time switch, ML_TIFF_NATIVE_SUPPORT, controls whether MLOffscreenCanvas uses libtiff to load TIFF files.
By default, ML_TIFF_NATIVE_SUPPORT is #defined to (!ML_USE_QUICKTIME).
Like MLJpg (the analogous glue class for the IJG library), MLTiff has a SetOrientation() method that allows me to
load a TIFF file rotated by 90, 180 or 270 degrees.
• 20040225: A new preferences key, DigitalEmotionApp::kKeyPreferencesUse2ndScreen ("use_2nd_screen") can be set to
false (default is true) to prevent Digital Emotion from using the second display, even if it's present.
• 20040301: Incorporated a first batch of changes from Alberto Barbati. Alberto is rewriting the Win32 drag-and-drop
code, which was previously broken and only partially implemented. Some pieces of the puzzle are still missing,
though, notably MLDrag::Start(). I asked Alberto to take care of those as well.
• 20040301: Added MLMessage::kEnter to the MLMessage::EFunctionKey enumeration, and modified MLMessage::IsFunctionKey()
so that this key (the Enter key on the numeric keypad) is correctly recognized on both platforms. This key generates
ASCII 3 on Mac OS, and the VK_RETURN virtual key code on Windows, where it can be told from the regular Return key
by testing bit 24 of the message LPARAM.
• 20040302: Second and final batch of changes from Alberto Barbati. Now we can rearrange lists by dragging lines,
move lines between lists, and even drag text from other programs (such as MS Word) into Digital Emotion views.
• 20040313: Reimplemented Win32 version of MLFileDialog::OpenDialog() so that the filter string is set correctly.
MLFileDialog::SetValidExtensions() now takes an optional array of human descriptions, currently only used on
Windows, used to build the file type drop-down list. Each string in the string array passed to SetValidExtensions()
can now represent multiple alternate extensions at once, separated by semicolons, e.g., ".jpg;.jpe;.jpeg".
• 20040315: Added MLApplication::PlaySound() function to play WAVE files (or memory-resident WAVE buffers), implemented
on both Windows (using ::PlaySound()) and Mac (using the QuickTime WAVE importer). This supersedes the Mac-only
kCommandPlayMacSound. Replaced the Mac-only "importer complete" sound resource that plays when an importer thread
is done with a WAVE file ("cashreg.wav") in the media folder.
• 20040329: Finally got around to implementing MLAffineTransformWithAlpha::EffectLoop32BitBilinear().
• 20040331: Revised MLJpegDecompressor class. Added an option to skip Exif parsing when this is not needed.
More importantly, the IJG library supports direct downscaling of JPEG (DCT) data (with scaling ratios of 1/2, 1/4 and 1/8)
without the need for separate decode and spatial downsampling steps. This means that the IJG library can generate a
1/N (with N = 2, 4 or 8) scaled-down image directly from a JPEG file, and it can do this much faster than it would
take to fully decode the JPEG file and then scale down the resulting full-size image. So far, the MLJpegDecompressor
class hadn't been exploiting this feature. Today I added a new SetScalingRatio() method that can be called before
the DoImport() step to instruct the IJG library to generate a scaled-down image. This feature is used in
DigitalEmotionPhoto::LoadMasterCanvas() to speed up the creation of "working" images from large masters.
• 20040401: For even faster direct downscaling of JPEGs, I applied Guido Vollbeding's patch to the IJG library, based on
ideas from Rakesh Dugad and Narendra Ahuja, and described here:
<http://jpegclub.org/jidctred/>
<http://vision.ai.uiuc.edu/~dugad/research/dct/index.html>
The only affected source file is "jidctred.c" -- the patched file is actually smaller than the original, and generates
tighter code.
Back to page 1