This commit is contained in:
Dagen Brock 2017-01-15 20:11:13 -06:00
commit d2f402501a
12 changed files with 339 additions and 34 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.DS_Store
build-buckshot-Desktop*

View File

@ -0,0 +1,41 @@
# buckshot
An image conversion tool for Mac OSX, Windows and Linux. Take modern image formats (PNG, JPEG, BMP, etc) and convert them for use in your Apple II programs or just for fun.
![Screenshot of starting the program](doc/web/Screenshot.png "Screenshot of starting the program")
# Usage
The fastest way to get started is to go here [https://apple2.gs/buckshot](https://apple2.gs/buckshot) where you can download the latest builds for Mac OSX, Windows and Linux.
Once you start the program, just "Open Source Image", select the "Apple ][ Display Mode" you want to convert your image to, and click "Preview", or even better, click the "Live Preview" checkbox to get real-time feedback on your conversion settings. Once you are satisfied with your conversion settings, click "Save Image File" to save in one of the Apple ][ image formats based on the display mode. If you want to save that image file directly to a ProDOS volume, that is now supported via the "Save To ProDOS" function! Then you can load it up in your favorit emulator, or transfer it to real disks/flash storage to view on glorious vintage hardware.
# Build
This project is written in C++ using the Qt framework. It calls out to two external binaries for image conversion ([http://www.appleoldies.ca/bmp2dhr/](b2d)) and ProDOS volume support ([http://www.brutaldeluxe.fr/products/crossdevtools/cadius/](CADIUS)).
You can build and run the project without it, but it won't be able to generate previews or save to ProDOS volumes without those 3rd-party binaries. The authors of those projects are not involved with this project, but have graciously encouraged my integration attempts with this software.
You will need to copy the binaries of those two programs for your platform to the build directory you are running.
Example for Mac OSX "Release" build:
```
cp ../b2d build-buckshot-Desktop_Qt_5_7_0_clang_64bit-Release/buckshot.app/Contents/MacOS/
cp ../Cadius build-buckshot-Desktop_Qt_5_7_0_clang_64bit-Release/buckshot.app/Contents/MacOS/
```
In this example I obviously have the files stored in a parent directory outside of the project folder.
If you can't find/build those binaries for your platform, I suggest you just pull them out of the downloads available at [https://apple2.gs/buckshot](https://apple2.gs/buckshot).
# Disclaimer
This was largely built as a proof-of-concept, and I tried to put it together quite quickly (the original version was over the course of about a week or two.) I would never suggest that this is ididiomatic C++ code or Qt code. I am very results oriented and wrote a lot of this quite procedurally, as I went along adding all my planned features. Please feel free to fork it and make fixes, clean it up, add in other converters, etc. I probably won't have much time to tinker with this one from here on out, as I have most of the features I need for my own usage at this point.
# Credits
I cannot begin to express my gratitude to the heavy lifters who authored the tools under-the-hood of buckshot.
### bmp2dhr
Thanks to Bill Buckels, whose seminal work in image conversion routines has long inspired me to improve my own IIgs conversion routines.
### CADIUS
Thanks to Brutal Deluxe, whose tools not only speed up my entire Apple II development pipeline, but also provides the ProDOS image support here.

BIN
assets/.DS_Store vendored

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 4.1.0, 2016-12-05T15:16:15. -->
<!-- Written by QtCreator 4.1.0, 2016-12-18T20:27:08. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>

View File

@ -5,9 +5,10 @@
#include "qdebug.h"
#include "qtimer.h"
#include "qmessagebox.h"
#include "qinputdialog.h"
const QString MainWindow::programName = QString("buckshot");
const QString MainWindow::version = QString("0.00");
const QString MainWindow::version = QString("0.02");
const QString MainWindow::imageName = QString("saved");
MainWindow::MainWindow(QWidget *parent) :
@ -137,6 +138,7 @@ void MainWindow::on_pushButton_sourceFilename_clicked()
QSize sourceSize = ui->label_source->pixmap()->size();
QString resolutionString = QString("%1 x %2").arg(sourceSize.width()).arg(sourceSize.height());
ui->label_sourceResolution->setText(resolutionString);
updateNeeded=1;
}
}
@ -233,7 +235,6 @@ void MainWindow::on_pushButton_preview_clicked()
QString converterPath = "/Users/dbrock/appleiigs/grlib/b2d";
//converterPath = QString("%1/b2d").arg(QDir::currentPath());
converterPath = QString("%1/b2d").arg(QCoreApplication::applicationDirPath());
QProcess process;
@ -333,7 +334,14 @@ void MainWindow::on_checkBox_livePreview_stateChanged(int arg1)
}
void MainWindow::on_pushButton_saveImage_pressed()
void MainWindow::on_actionWhat_is_this_triggered()
{
QMessageBox msgBox;
msgBox.setText("This is an image conversion utility to output images for use on classic 8-bit Apple II computers.\n\nPlease see readme for instructions.\n\nSorry for bugs, it's just a toy.\n\n(c)2016 Dagen Brock *\n\n\n * bmp2dhr is by Bill Buckles and does the the actual heavy lifting of conversion! But don't bug him about this software, please.");
msgBox.exec();
}
void MainWindow::on_pushButton_saveImage_clicked()
{
if (ui->label_preview->pixmap() == NULL) {
ui->plainTextEdit_lastCmd->document()->setPlainText("Please open a source image and run a preview first!");
@ -372,9 +380,248 @@ void MainWindow::on_pushButton_saveImage_pressed()
}
void MainWindow::on_actionWhat_is_this_triggered()
// HOLY CRAP WHAT IS THIS??
// Well you see, Johnny, this is a layer of hacks to try
// to wrap CADIUS, but it has no API. So I can clean this up
// but it's more of a proof of concept. I think it'd be
// smarter to add JSON output to CADIUS in the long run.
// I consider this small feature a present to the community.
void MainWindow::on_pushButton_saveToProdos_clicked()
{
QMessageBox msgBox;
msgBox.setText("This is an image conversion utility to output images for use on classic 8-bit Apple II computers.\n\nPlease see readme for instructions.\n\nSorry for bugs, it's just a toy.\n\n(c)2016 Dagen Brock *\n\n\n * bmp2dhr is by Bill Buckles and does the the actual heavy lifting of conversion! But don't bug him about this software, please.");
msgBox.exec();
// @Todo: This isn't appropriate when someone changes res/source and has
// a previous preview pixmap, it will think we are all OK.
if (ui->label_preview->pixmap() == NULL) {
ui->plainTextEdit_lastCmd->document()->setPlainText("Please open a source image and run a preview first!");
return;
}
QString cadiusPath = "/Users/dbrock/appleiigs/tools/Cadius";
cadiusPath = QString("%1/Cadius").arg(QCoreApplication::applicationDirPath());
// KSYNTHED=Type(06),AuxType(2000),VersionCreate(70),MinVersion(BE),Access(E3),FolderInfo1(000000000000000000000000000000000000),FolderInfo2(000000000000000000000000000000000000)
QString filetype = "06";
QString auxtype = "2000";
QString suffix = ".po";
QString defaultFilter = tr("All ProDOS Images (*.po *.hdv *.2mg)");
QString filters = QString(tr("All ProDOS Images (*.po *.hdv *.2mg);;ProDOS Order (*.po);;HDV (*.hdv);;2MG (*.2mg);;All files (*.*)"));
// PROMPT FOR SAVE FILENAME AND COPY (HOPEFULLY) TO SAVE FILENAME
QString prodosImageFile = QFileDialog::getSaveFileName(0, "Choose ProDOS Image to Save to", QDir::currentPath(), filters, &defaultFilter, QFileDialog::DontConfirmOverwrite);
// ALSO GENERATE PRODOS SAFE BASENAME
QFileInfo fi(prodosImageFile);
QString prodosVolumeName = fi.baseName().left(15); // get max volume name
// EMPTY FILENAME?!
if (prodosImageFile.length() == 0) {
return;
}
QString imageSize = "140KB";
// NOW SEE IF IT'S A NEW FILE OR EXISTING IMAGE
QFileInfo check_file(prodosImageFile);
// check if file exists and if yes: Is it really a file and no directory?
if (check_file.exists() && check_file.isFile()) {
// nothing yet. We'll open it below, either way.
} else {
// NEW FILE, PROMPT FOR IMAGE SIZE
QMessageBox msgBox;
msgBox.setText(tr("Select size for new ProDOS image"));
msgBox.addButton(tr("Cancel"), QMessageBox::NoRole);
QAbstractButton* pButton140 = msgBox.addButton(tr("140KB"), QMessageBox::ApplyRole);
QAbstractButton* pButton800 = msgBox.addButton(tr("800KB"), QMessageBox::ApplyRole);
msgBox.setDefaultButton(QMessageBox::Yes);
msgBox.exec();
if (msgBox.clickedButton()==pButton140) {
// set above, nothing to do
} else if (msgBox.clickedButton()==pButton800) {
imageSize = "800KB";
} else {
return; // cancelled
}
// NOW CREATE AN IMAGE
QProcess process;
QStringList args;
args << "CREATEVOLUME";
args << prodosImageFile;
args << prodosVolumeName;
args << imageSize;
QString commandString = QString("%1 %2").arg(cadiusPath, args.join(" "));
// RUN THE IMAGE CREATE
process.start(cadiusPath,args);
process.waitForFinished(); // BLOCKS!!!
// NOW CHECK AGAIN TO SEE IF OUR IMAGE FILE GOT CREATED
if (check_file.exists() && check_file.isFile()) {
qDebug() << "IMAGE FILE CREATED.";
} else {
ui->plainTextEdit_lastCmd->document()->setPlainText(QString("Failed creating image with command: %1").arg(commandString));
return;
}
ui->plainTextEdit_lastCmd->document()->setPlainText(commandString);
}
// NOW CATALOG WHATEVER IMAGE WE GOT... NEW/EXISTING
QProcess cat_process;
QStringList cat_args;
cat_args << "CATALOG";
cat_args << prodosImageFile;
// RUN THE CATALOG AND GET OUTPUT
cat_process.start(cadiusPath,cat_args);
cat_process.waitForFinished(); // BLOCKS!!!
QString cat_output = QString(cat_process.readAllStandardOutput());
qDebug() << "CATALOG OUTPUT (cat_output)\n " << cat_output;
// regex scanner index
int pos = 0;
QStringList list;
// MUST MATCH FOR NEWLINES (VS USING ^ or &)
QRegExp vol_rx("\n(/.{1,15}/)\n");
// OVERWRITE PRODOS VOLUME NAME IF WE KNOW BETTER
while ((pos = vol_rx.indexIn(cat_output, pos)) != -1) {
prodosVolumeName = vol_rx.cap(1);
break;
}
// NOW MATCH FOR SOME EXTRA DETAILS JUST BECAUSE WE CAN
QRegExp rx("(Block|Free|File|Directory) : (\\d+)");
pos = 0;
while ((pos = rx.indexIn(cat_output, pos)) != -1) {
list << rx.cap(1);
qDebug() << rx.cap(1);
pos += rx.matchedLength();
}
int diskBlocks, diskFree, diskFiles, diskDirs = 0;
if (list.length() == 4) {
diskBlocks = list[0].toInt();
diskFree = list[1].toInt();
diskFiles = list[2].toInt();
diskDirs = list[3].toInt();
} else {
ui->plainTextEdit_lastCmd->document()->setPlainText(QString("Failed to catalog ProDOS volume. Make sure that the file is one of: .po .hdv .2mg"));
return;
}
QString a2Filename;
QString savedFilename;
if (ui->comboBox_outputFormat->currentText() == "LR") {
savedFilename = QString("%1/%2.SLO").arg(tmpDirPath,imageName.toUpper());
a2Filename = QString("%1.SLO").arg(imageName.toUpper());
auxtype = "400"; // different auxtype (not that is matters)
} else if (ui->comboBox_outputFormat->currentText() == "DLR") {
savedFilename = QString("%1/%2.DLO").arg(tmpDirPath,imageName.toUpper());
a2Filename = QString("%1.DLO").arg(imageName.toUpper());
auxtype = "400"; // different auxtype (not that is matters)
} else if (ui->comboBox_outputFormat->currentText() == "HGR") {
savedFilename = QString("%1/%2CH.BIN").arg(tmpDirPath,imageName.toUpper());
a2Filename = QString("%1CH.BIN").arg(imageName.toUpper());
} else if (ui->comboBox_outputFormat->currentText() == "DHGR") {
savedFilename = QString("%1/%2.A2FC").arg(tmpDirPath,imageName.toUpper());
a2Filename = QString("%1.A2FC").arg(imageName.toUpper());
} else if (ui->comboBox_outputFormat->currentText() == "MONO") {
savedFilename = QString("%1/%2M.BIN").arg(tmpDirPath,imageName.toUpper());
a2Filename = QString("%1M.BIN").arg(imageName.toUpper());
}
bool ok;
QString prodosFileName = QInputDialog::getText(this, tr("Save Image to ProDOS as"),
tr("ProDOS Name (max 15 chars):"), QLineEdit::Normal,
a2Filename, &ok);
if (ok && !prodosFileName.isEmpty()) {
// COPY IT ... OVER EXISTING NAME?
QString saveFile = QString("%1/%2").arg(tmpDirPath,prodosFileName);
QFile::copy(savedFilename, saveFile);
// GENERATE OUR STUPID _FileInformation.txt in the same directory
QString fileinfo_text = QString("%1=Type(%2),AuxType(%3),VersionCreate(70),MinVersion(BE),Access(E3),FolderInfo1(000000000000000000000000000000000000),FolderInfo2(000000000000000000000000000000000000)").arg(prodosFileName, filetype, auxtype);
QString fileinfo_file = QString("%1/_FileInformation.txt").arg(tmpDirPath);
qDebug() << "TMP FILE: " << fileinfo_file;
QFile file( fileinfo_file );
if (file.open(QIODevice::ReadWrite)) {
QTextStream stream( &file );
stream << fileinfo_text << endl;
}
// NOW ADD / SAVE OUR FILE
QProcess addfile_process;
QStringList addfile_args;
addfile_args << "ADDFILE" << prodosImageFile << prodosVolumeName << saveFile; // our tmp file
addfile_process.start(cadiusPath,addfile_args);
addfile_process.waitForFinished(); // BLOCKS!!!
QString addfile_output = QString(addfile_process.readAllStandardOutput());
// IF FILE ALREADY EXISTS, ASK IF THEY WANT TO OVERWRITE
if (addfile_output.contains("A file already exist with the same nam")) {
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this, "File exists in image", "File exists, Replace?",
QMessageBox::Yes|QMessageBox::No);
if (reply == QMessageBox::Yes) {
// ../tools/Cadius DELETEFILE <[2mg|hdv|po]_image_path> <prodos_file_path>
// YES - DELETE
QString deleteFile = QString("%1%2").arg(prodosVolumeName,prodosFileName);
qDebug() << "DELETEFILE : " <<deleteFile;
QProcess delfile_process;
QStringList delfile_args;
delfile_args << "DELETEFILE";
delfile_args << prodosImageFile;
delfile_args << deleteFile;
// NOW ADD / SAVE OUR FILE
delfile_process.start(cadiusPath,delfile_args);
delfile_process.waitForFinished(); // BLOCKS!!!
QString delfile_output = QString(delfile_process.readAllStandardOutput());
// MAYBE CHECK? BUT WE CAN JUST TRY TO RESAVE AND FAIL THERE
// TRY ADDFILE AGAIN
QProcess addfile2_process;
QStringList addfile2_args;
addfile2_args << "ADDFILE" << prodosImageFile << prodosVolumeName << saveFile; // our tmp file
addfile2_process.start(cadiusPath,addfile_args);
addfile2_process.waitForFinished(); // BLOCKS!!!
QString addfile2_output = QString(addfile2_process.readAllStandardOutput());
// IF FILE ALREADY EXISTS, ASK IF THEY WANT TO OVERWRITE
if (addfile2_output.contains("Error :")) {
ui->plainTextEdit_lastCmd->document()->setPlainText("Save failed. Couldn't delete/overwrite file? I really am not sure what went wrong. Try saving the pictures to your computer and transferring them to ProDOS with another program.");
return;
}
} else {
if (ui->label_preview->pixmap() == NULL) {
ui->plainTextEdit_lastCmd->document()->setPlainText("Save cancelled because file exists.");
return;
}
}
}
// IF WE MADE IT THIS FAR, ALL GOOD?
QFile::remove(saveFile); // remove our tmp file
ui->plainTextEdit_lastCmd->document()->setPlainText("File saved.");
} else {
ui->plainTextEdit_lastCmd->document()->setPlainText("Save cancelled.");
// cancelled?
}
return;
}

View File

@ -26,22 +26,21 @@ private slots:
void on_pushButton_sourceFilename_clicked();
void on_pushButton_preview_clicked();
void on_horizontalSlider_crossHatch_valueChanged(int value);
void on_horizontalSlider_colorBleed_valueChanged(int value);
void on_comboBox_outputFormat_currentIndexChanged(int index);
void on_comboBox_inputResolution_currentIndexChanged(int index);
void on_checkBox_livePreview_stateChanged(int arg1);
void previewTimerTimeout();
void on_pushButton_saveImage_pressed();
void on_actionWhat_is_this_triggered();
void on_pushButton_preview_clicked();
void on_pushButton_saveImage_clicked();
void on_pushButton_saveToProdos_clicked();
private:
Ui::MainWindow *ui;
void updateInputSize();

View File

@ -97,9 +97,9 @@
<widget class="QPushButton" name="pushButton_preview">
<property name="geometry">
<rect>
<x>550</x>
<x>530</x>
<y>260</y>
<width>111</width>
<width>131</width>
<height>32</height>
</rect>
</property>
@ -136,7 +136,7 @@
<widget class="QCheckBox" name="checkBox_livePreview">
<property name="geometry">
<rect>
<x>440</x>
<x>420</x>
<y>260</y>
<width>101</width>
<height>30</height>
@ -351,14 +351,14 @@
<widget class="QPushButton" name="pushButton_saveImage">
<property name="geometry">
<rect>
<x>550</x>
<x>530</x>
<y>290</y>
<width>111</width>
<width>131</width>
<height>32</height>
</rect>
</property>
<property name="text">
<string>Save Image</string>
<string>Save Image File</string>
</property>
</widget>
<widget class="Line" name="line">
@ -374,6 +374,19 @@
<enum>Qt::Vertical</enum>
</property>
</widget>
<widget class="QPushButton" name="pushButton_saveToProdos">
<property name="geometry">
<rect>
<x>530</x>
<y>320</y>
<width>131</width>
<height>32</height>
</rect>
</property>
<property name="text">
<string>Save To ProDOS</string>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">

View File

@ -1,4 +0,0 @@
This is an image conversion utility by Dagen Brock.
It is actually just a GUI layer over bmp2dhgr by Bill Buckles.
Don't bug him with support questions for this application.

BIN
doc/web/Screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 KiB

View File

@ -3,21 +3,29 @@ DDIR=buckshot-osx/buckshot.app
ADIR=assets
mkdir -p $DDIR
# make app bundle with qt frameworks using macdeployqt
~/Qt/5.7/clang_64/bin/macdeployqt build-buckshot-Desktop_Qt_5_7_0_clang_64bit-Release/buckshot.app
# copy to dmg staging dir
cp -R build-buckshot-Desktop_Qt_5_7_0_clang_64bit-Release/buckshot.app $DEXTRAS
mkdir -p $DDIR/Contents/MacOS
mkdir -p $DDIR/Contents/Resources
cp $ADIR/Info.plist $DDIR/Contents
cp $ADIR/icons.icns $DDIR/Contents/Resources
dylibbundler -od -b -x $DDIR/Contents/MacOS/buckshot -d $DDIR/Contents/libs/
# not needed?
#dylibbundler -od -b -x $DDIR/Contents/MacOS/buckshot -d $DDIR/Contents/libs/
# files to include in dmg
#cp doc/gsplusmanual.pdf $DEXTRAS
#cp doc/gsplusmanual.txt $DEXTRAS
cp doc/README.txt $DEXTRAS
cp ../b2d $DDIR/Contents/MacOS
#cp COPYING.txt $DEXTRAS
cp README.txt $DEXTRAS
cp LICENSE.txt $DEXTRAS
#tar -cvzf gsplus-osx.tar.gz gsplus-osx/
# COPY BINARIES FROM EXTERNAL PROJECTS
cp ../b2d $DDIR/Contents/MacOS
cp ../tools/Cadius $DDIR/Contents/MacOS
# packaging now in DMG script

View File

@ -6,17 +6,15 @@ test -f buckshot.dmg && rm buckshot.dmg
./create-dmg \
--volname "buckshot" \
--volicon "../assets/icons.icns" \
--background "../../gsplus/assets/gsp_dmg_bg_600x500.png" \
--background "../assets/buckshot_dmg_bg_600x500.png" \
--window-pos 200 120 \
--window-size 600 500 \
--icon-size 100 \
--icon buckshot.app 180 130 \
--hide-extension buckshot.app \
--icon README.txt 80 330 \
--icon README.txt 200 330 \
--icon LICENSE.txt 410 330 \
--app-drop-link 410 130 \
buckshot.dmg \
../buckshot-osx/
cp buckshot.dmg ..
#--icon gsplusmanual.pdf 220 330 \
#--icon gsplusmanual.txt 360 330 \
#--icon COPYING.txt 500 330 \