a, a:visited, a:hover, a:active { color: var(--text); }
hr {
border: 1px solid var(--text);
margin: 2em 0;
}
p.errorMessage{
background-color: var(--errorBackground);
border: 1px dashed var(--errorText);
color: var(--errorText);
border-radius: 10px;
padding: 10px;
margin: 20px;
}
h1:first-child { text-align: center; }
p:last-child { text-align: center; }
.controlContainer {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.control {
display: inline;
background-color: var(--mappingBox);
padding: 1em;
border-radius: 1em;
margin: 0.5em;
}
.control input[type=number] {width: 4em;}
#processFilePreview {
border: 1px white dashed;
}
dl {margin: 2em;}
dt {font-weight: bold;}
dd {margin-bottom: 1em;}
</style>
</head>
<body>
<h1>Data Frog SF2000 Generic Image Tool</h1>
<p>This tool is a generic image conversion tool for the Data Frog SF2000. You can use it to convert the SF2000's raw image data files to PNG, and convert PNG/JPEG images to RGB565 format (a reduced colour mode used for most of the SF2000's UI elements), or BGRA format (the flavour of transparent images used by some SF2000 UI elements). You can also choose a variety of scaling or filtering modes when converting to an SF2000 image. Please note this tool is provided as-is; make sure you have backups of anything you care about before potentially replacing any files on your SF2000's microSD card! 🙂</p>
<hr>
<divid="steps">
<sectionid="modeSection">
<h2>Step 1: Select Operating Mode</h2>
<p>This tool can operate in two main modes - you can use it to convert <em>from</em> an SF2000 format <em>to</em> RGB888/RGBA, or you can use it to convert <em>to</em> an SF2000 format <em>from</em> RGB888/RGBA. Choose the mode you want, and follow the next instruction that appears.</p>
// Add the appropriate Step 2 depending on what was selected...
if (this.value == "fromSF2000") {
setupStepTwo_From();
}
else if (this.value == "toSF2000") {
setupStepTwo_To();
}
});
}
// This function sets up the HTML for "Convert from SF2000 > Step 2", selecting
// an image file from the SF2000's Resources folder that the user wants to
// convert to RGB888/RGBA...
function setupStepTwo_From() {
// Create the new section, add our heading and our instruction paragraph...
var html = "<sectionid=\"selectSF2000File\"><h2>Step 2: Select SF2000 Image File</h2><p>Select the SF2000 image file you want to convert from. If you need help choosing the image you want to convert, you can <ahref=\"https://vonmillhausen.github.io/sf2000/#images-used\"target=\"_blank\"rel=\"noreferrernoopener\">find a reference to all of the used UI images in my overview of the SF2000 here</a>.</p><divid=\"step2Messages\"></div>";
// Add our file chooser...
html += "<divclass=\"controlContainer\"><labelclass=\"control\"><inputid=\"inputImage\"type=\"file\"></label></div>";
// Close off our section...
html += "</section>";
// Finally, add a <hr> separator after the last step, and append the new step...
// The file the user selected doesn't appear to be binary data, so
// it can't be an SF2000 image file; let the user know...
document.getElementById("step2Messages").innerHTML = "<pclass=\"errorMessage\">ERROR: The selected file does not appear to be binary file; please make sure you're selecting an SF2000 encoded image file.</p>";
return;
}
}
});
}
// This function sets up the HTML for "Convert to SF2000 > Step 2", selecting
// the image file the user wants to convert to one of the SF2000's image
// formats...
function setupStepTwo_To() {
// Start our new section, add our header and our instructions...
var html = "<sectionid=\"selectImageFile\"><h2>Step 2: Select RGB888/RGBA Image File</h2><p>Select the image file you want to convert to an SF2000 image format. You can select either a PNG or JPEG format image. Transparency is respected for PNGs, but only if outputting to the BGRA output format.</p><divid=\"step2Messages\"></div>";
// Add our file chooser...
html += "<divclass=\"controlContainer\"><labelclass=\"control\"><inputid=\"inputImage\"type=\"file\"></label></div>";
// Close our section...
html += "</section>";
// Finally, add a <hr> separator after the last step, and append the new step...
// The file is loaded; let's check to make sure we got a PNG or JPEG...
if (!event.target.result.includes("data:image/png;") && !event.target.result.includes("data:image/jpeg;")) {
// Whoops! Doesn't look like the user provided a PNG or a JPEG!
document.getElementById("step2Messages").innerHTML = "<pclass=\"errorMessage\">ERROR: The selected file does not appear to be either a PNG or JPEG image; please make sure you're selecting an appropriate image file.</p>";
return;
}
// Now we're going to load the file's data into an Image object, so we can
// access the raw image data...
userImageData = new Image;
userImageData.src = event.target.result;
userImageData.onload = function(event) {
// To access the raw image data, we jam the image into a Canvas object,
// and then grab an ImageData object from the Canvas. This will give us
// access to the raw RGBA data for each pixel in the image...
var workingArea = document.createElement("canvas");
// Create the new section, add our heading and our instruction paragraph...
var html = "<sectionid=\"processSF2000File\"><h2>Step 3: Set Data Interpretation Options</h2><p>As the image files on the SF2000 are raw binary blobs, there's no information stored in them to say what their width or height is, what format the pixel information is stored in, etc.. Therefore, it is up to <em>you</em> to specify those details yourself, which you can do using the options below.</p><p>A preview of the image will appear below the options, reflecting the current options; the preview has a white dash outline so you can better judge where the bounds of transparent images are. Below the preview image is a \"Download\" button you can use to download the image as a PNG.</p><p>Depending on the size of the file you selected, this tool may recognise it as a \"known\" file from the SF2000, and if so the options below will have been set automatically for you. If not, or if the options are automatically set incorrectly, here's a brief description of what each option does:</p><dl><dt>Image Format:</dt><dd>Specifies the data format for each pixel in the image. There are two image formats currently used by the SF2000 - RGB565 (used for most of the \"basic\" UI elements, including main backgrounds, etc.), and BGRA (used for anything that requires transparency, such as the system logos on the main menu, etc.).</dd><dt>Width:</dt><dd>Specifies the width of the image in pixels.</dd><dt>Height:</dt><dd>Specifies the height of the image in pixels.</dd></dl>";
// Next, let's add our image controls; a select list for the image format,
// and number inputs for the interpreted width and height...
html += "<divclass=\"controlContainer\">";
html += "<divclass=\"control\"><label>Image Format: <selectid=\"imageFormat\"><option"+(imageFormat =="RGB565"?"selected":"")+">RGB565</option><option"+(imageFormat =="BGRA"?"selected":"")+">BGRA</option></select></label></div>";
html += "<divclass=\"control\"><label>Width: <inputid=\"outputImageWidth\"type=\"number\"min=\"1\"step=\"1\"value=\""+outputImageWidth+"\"></label></div>";
html += "<divclass=\"control\"><label>Height: <inputid=\"outputImageHeight\"type=\"number\"min=\"1\"step=\"1\"value=\""+outputImageHeight+"\"></label></div>";
html += "</div>";
// Next, we'll add the image preview...
html += "<divclass=\"controlContainer\"><divclass=\"control\"><canvasid=\"processFilePreview\"></canvas></div></div>";
// ... and the Download button...
html += "<divclass=\"controlContainer\"><divclass=\"control\"><inputid=\"sf2000Download\"type=\"button\"value=\"Download\"></div></div>";
// ... and lastly we'll close off our section...
html += "</section>";
// Add a <hr> separator after the previous step, and append the new step...
var canvas = document.getElementById("processFilePreview");
var context = canvas.getContext("2d");
canvas.width = outputImageWidth;
canvas.height = outputImageHeight;
convertImageFromSF2000();
renderImageToCanvas();
}
// This function sets up the HTML for "Convert to SF2000 > Step 2 > Step 3"...
function setupStepThree_To_SF2000Image() {
// In this section, we'll be rendering the HTML for some image processing
// controls, as well as an image preview. As we're coming from a known
// image, we already have our width and height this time, and so we can
// just get on to rendering the HTML. Create our new section, add its
// heading and instruction paragraphs...
html = "<sectionid=\"processUserFile\"><h2>Step 3: Set Conversion Options</h2><p>The options below control how your image is converted for use on the SF2000. A preview of the image will appear below the options, reflecting their current settings; the preview has a white dash outline so you can better judge where the bounds of transparent images are. Below the preview image is a \"Download\" button you can use to download the image as binary blob for the SF2000. If you name your input image like <code>name.extension.extension</code> (e.g., <code>c1eac.pal.png</code>), then the download will automatically be named like <code>name.extension</code> (e.g., <code>c1eac.pal</code>); this may help to speed up your workflow.</p><p>Here's a brief description of what each option does:</p><dl><dt>Image Format:</dt><dd>Specifies the data format for the output image. There are two image formats currently used by the SF2000 - RGB565 (used for most of the \"basic\" UI elements, including main backgrounds, etc.), and BGRA (used for anything that requires transparency, such as the system logos on the main menu, etc.). If you're not sure which format to choose, <ahref=\"https://vonmillhausen.github.io/sf2000/#images-used\"target=\"_blank\"rel=\"noreferrernoopener\">refer to my list of images used by the SF2000</a>.</dd><dt>Scaling:</dt><dd>One of two available scaling modes for your input image; the \"Scaling\" option lets you scale your image maintaining aspect ratio amongst several common sizes (e.g., 1x scaling to maintain the input image size, 2x scaling to double it, etc.)</dd><dt>Fit to:</dt><dd>The other available scaling mode; this will scale your image to the specified width and height, <em>without</em> maintaining aspect ratio. This will allow you to scale an image to any dimension you want, but it'll be up to you to do your own aspect ratio calculations.</dd><dt>Filter Type:</dt><dd>When scaling an image by anything other than 1x, specifies the type of image filtering to use. \"Nearest Neighbour\" will give sharp pixel scaling but only at integer upscales; any other scale will appear aliased. \"Bilinear\" will give a fuzzier scale, but it works better for non-integer scale factors and for downscaling.</dd></dl>";
// Now let's add our image controls; there's a few of them! We have a
// select box for choosing the image format, we have two different
// scaling options (one with a select box for choosing pre-defined
// scales, and one for user-defined output size), and we have a select
// box for choosing nearest-neighbour or hybrid gaussian/bilinear
// image filtering...
html += "<divclass=\"controlContainer\">";
// Image format...
html += "<divclass=\"control\"><label>Image Format: <selectid=\"imageFormat\"><option"+(imageFormat =="RGB565"?"selected":"")+">RGB565</option><option"+(imageFormat =="BGRA"?"selected":"")+">BGRA</option></select></label></div>";
html += "<divclass=\"control\"><label><inputtype=\"radio\"name=\"scaleMode\"value=\"fit\""+(userScaleMode =="fit"?"checked":"")+"> Fit to: </label><label>width <inputid=\"userFitWidth\"type=\"number\"min=\"1\"step=\"1\"value=\""+userFitWidth+"\""+(userScaleMode =="scale"?"disabled":"")+"></label> and <label>height <inputid=\"userFitHeight\"type=\"number\"min=\"1\"step=\"1\"value=\""+userFitHeight+"\""+(userScaleMode =="scale"?"disabled":"")+"></label></div>";
html += "</div>";
// Filter type...
html += "<divclass=\"control\"><label>Filter Type: <selectid=\"userFilterType\"><option"+(userFilterType =="NearestNeighbour"?"selected":"")+">Nearest Neighbour</option><option"+(userFilterType =="Bilinear"?"selected":"")+">Bilinear</option></select></label></div>";
html += "</div>";
// Next we'll add our image preview...
html += "<divclass=\"controlContainer\"><divclass=\"control\"><canvasid=\"processFilePreview\"></canvas></div></div>";
// ... and our Download button...
html += "<divclass=\"controlContainer\"><divclass=\"control\"><inputid=\"userDownload\"type=\"button\"value=\"Download\"></div></div>";
// ... and lastly we'll close off our section...
html += "</section>";
// Add a <hr> separator after the previous step, and append the new step...