From b4541d2252c7aa26ea81e3a613545f3b2cce6695 Mon Sep 17 00:00:00 2001
From: sam <sam@cinelerra-gg.org>
Date: Wed, 14 Oct 2026 16:53:22 +0200
Subject: [PATCH] FileDPX: write frames

FileDPX could only read.  A render to DPX created empty files, because
FileList::write_frame() does nothing by default, and the format was not
offered as a render format.

write_frame() now writes 16 bit RGB through the libdpx Writer into the
compressed buffer of the frame, the way FileEXR does it.  Other colour
models are converted first.  DPX and DPX Sequence join File::renders_video(),
File::is_image_render() and the format popup.

Two fixes in the same file: DPXInStream::ReadDirect() had no return, and
FileDPX::color_model was not set before the first frame was read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---
 cinelerra-5.1/cinelerra/file.C        |   3 +
 cinelerra-5.1/cinelerra/filedpx.C     | 112 +++++++++++++++++++++++++-
 cinelerra-5.1/cinelerra/filedpx.h     |   1 +
 cinelerra-5.1/cinelerra/formatpopup.C |   4 +
 4 files changed, 119 insertions(+), 1 deletion(-)

diff --git a/cinelerra-5.1/cinelerra/file.C b/cinelerra-5.1/cinelerra/file.C
index 98ca34db..1ccbc576 100644
--- a/cinelerra-5.1/cinelerra/file.C
+++ b/cinelerra-5.1/cinelerra/file.C
@@ -1516,6 +1516,8 @@ int File::renders_video(int format)
 	case FILE_JPEG_LIST:
 	case FILE_CR2:
 	case FILE_CR2_LIST:
+	case FILE_DPX:
+	case FILE_DPX_LIST:
 	case FILE_EXR:
 	case FILE_EXR_LIST:
 	case FILE_GIF:
@@ -1571,6 +1573,7 @@ int File::renders_audio(Asset *asset)
 int File::is_image_render(int format)
 {
 	switch( format ) {
+	case FILE_DPX:
 	case FILE_EXR:
 	case FILE_JPEG:
 	case FILE_PNG:
diff --git a/cinelerra-5.1/cinelerra/filedpx.C b/cinelerra-5.1/cinelerra/filedpx.C
index 2556e9ce..5b042be5 100644
--- a/cinelerra-5.1/cinelerra/filedpx.C
+++ b/cinelerra-5.1/cinelerra/filedpx.C
@@ -80,7 +80,7 @@ size_t DPXInStream::Read(void * buf, const size_t size)
 
 size_t DPXInStream::ReadDirect(void * buf, const size_t size)
 {
-	this->Read(buf, size);
+	return this->Read(buf, size);
 }
 
 bool DPXInStream::EndOfFile() const
@@ -122,9 +122,81 @@ bool DPXInStream::Seek(long offset, Origin origin)
 
 
 
+// Collects the file in the compressed buffer of a frame, the way the
+// OpenEXR stream does.  The writer seeks back to patch the header.
+class DPXOutStream : public OutStream
+{
+public:
+	DPXOutStream(VFrame *data);
+	virtual ~DPXOutStream();
+
+	bool Open(const char *fn);
+	void Close();
+	size_t Write(void * buf, const size_t size);
+	bool Seek(long offset, Origin origin);
+	void Flush();
+
+private:
+	VFrame *data;
+	long pos;
+};
+
+DPXOutStream::DPXOutStream(VFrame *data) :
+	data(data),
+	pos(0)
+{
+}
+
+DPXOutStream::~DPXOutStream()
+{
+}
+
+bool DPXOutStream::Open(const char *fn)
+{
+	return true;
+}
+
+void DPXOutStream::Close()
+{
+}
+
+size_t DPXOutStream::Write(void * buf, const size_t size)
+{
+	long end = pos + (long)size;
+	if( end > data->get_compressed_allocated() )
+		data->allocate_compressed_data(MAX(end, data->get_compressed_allocated() * 2));
+	// a seek past the end leaves a gap: it reads as zeros
+	long used = data->get_compressed_size();
+	if( pos > used )
+		memset(data->get_data() + used, 0, pos - used);
+	memcpy(data->get_data() + pos, buf, size);
+	pos = end;
+	data->set_compressed_size(MAX(end, used));
+	return size;
+}
+
+bool DPXOutStream::Seek(long offset, Origin origin)
+{
+	long to = 0;
+	switch( origin ) {
+	case kStart:   to = offset; break;
+	case kCurrent: to = pos + offset; break;
+	case kEnd:     to = data->get_compressed_size() + offset; break;
+	}
+	if( to < 0 ) return false;
+	pos = to;
+	return true;
+}
+
+void DPXOutStream::Flush()
+{
+}
+
+
 FileDPX::FileDPX(Asset *asset, File *file)
  : FileList(asset, file, "DPXLIST", ".dpx", FILE_DPX, FILE_DPX_LIST)
 {
+	color_model = BC_RGB161616;
 	if(asset->format == FILE_UNKNOWN) 
 		asset->format = FILE_DPX_LIST;
 }
@@ -196,6 +268,44 @@ int FileDPX::read_frame_header(char *path)
 	return result;
 }
 
+int FileDPX::write_frame(VFrame *frame, VFrame *data, FrameWriterUnit *unit)
+{
+	data->set_compressed_size(0);
+	int w = frame->get_w(), h = frame->get_h();
+
+	// the file takes 16 bit RGB: convert what the renderer hands over
+	VFrame *temp = 0, *src = frame;
+	if( frame->get_color_model() != BC_RGB161616 ) {
+		temp = new VFrame(w, h, BC_RGB161616, 0);
+		BC_CModels::transfer(temp->get_rows(), frame->get_rows(),
+			temp->get_y(), temp->get_u(), temp->get_v(),
+			frame->get_y(), frame->get_u(), frame->get_v(),
+			0, 0, w, h, 0, 0, w, h,
+			frame->get_color_model(), BC_RGB161616, 0, w, h);
+		src = temp;
+	}
+
+	// the writer wants the rows packed, a frame may pad them
+	uint16_t *pixels = new uint16_t[(size_t)w * h * 3];
+	unsigned char **rows = src->get_rows();
+	for( int y=0; y<h; ++y )
+		memcpy(pixels + (size_t)y * w * 3, rows[y], (size_t)w * 3 * sizeof(uint16_t));
+
+	DPXOutStream outStream(data);
+	dpx::Writer writer;
+	writer.SetOutStream(&outStream);
+	writer.Start();
+	writer.SetFileInfo("frame.dpx", 0, "Cinelerra-GG");
+	writer.SetImageInfo(w, h);
+	writer.SetElement(0, dpx::kRGB, 16, dpx::kUserDefined, dpx::kUserDefined);
+	int result = writer.WriteHeader() &&
+		writer.WriteElement(0, pixels, dpx::kWord) &&
+		writer.Finish() ? 0 : 1;
+	delete [] pixels;
+	delete temp;
+	return result;
+}
+
 int FileDPX::read_frame(VFrame *frame, VFrame *data)
 {	
 	DPXInStream inStream((char*)data->get_data(), data->get_compressed_size());
diff --git a/cinelerra-5.1/cinelerra/filedpx.h b/cinelerra-5.1/cinelerra/filedpx.h
index 8db92c77..bef0557d 100644
--- a/cinelerra-5.1/cinelerra/filedpx.h
+++ b/cinelerra-5.1/cinelerra/filedpx.h
@@ -42,6 +42,7 @@ public:
 	int colormodel_supported(int colormodel);
 	int read_frame_header(char *path);
 	int read_frame(VFrame *frame, VFrame *data);
+	int write_frame(VFrame *frame, VFrame *data, FrameWriterUnit *unit);
 
 private:
 	int color_model;
diff --git a/cinelerra-5.1/cinelerra/formatpopup.C b/cinelerra-5.1/cinelerra/formatpopup.C
index 90f6599a..15d5f806 100644
--- a/cinelerra-5.1/cinelerra/formatpopup.C
+++ b/cinelerra-5.1/cinelerra/formatpopup.C
@@ -68,6 +68,10 @@ void FormatPopup::create_objects()
 #ifdef HAVE_OPENEXR
 		post_item(FILE_EXR);
 		post_item(FILE_EXR_LIST);
+#endif
+#ifdef HAVE_LIBDPX
+		post_item(FILE_DPX);
+		post_item(FILE_DPX_LIST);
 #endif
 		post_item(FILE_WAV);
 		post_item(FILE_RAWDV);
-- 
2.43.0

